decidim-only_forms 0.1.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 +108 -0
- data/Rakefile +87 -0
- data/app/commands/decidim/forms/answer_questionnaire.rb +112 -0
- data/app/commands/decidim/forms/command.rb +14 -0
- data/app/controllers/decidim/forms/concerns/has_questionnaire.rb +154 -0
- data/app/models/decidim/surveys/survey.rb +40 -0
- data/config/i18n-tasks.yml +10 -0
- data/config/locales/en.yml +27 -0
- data/config/locales/fr.yml +27 -0
- data/config/locales/pt.yml +27 -0
- data/lib/decidim/only_forms/admin.rb +10 -0
- data/lib/decidim/only_forms/admin_engine.rb +21 -0
- data/lib/decidim/only_forms/component.rb +41 -0
- data/lib/decidim/only_forms/engine.rb +18 -0
- data/lib/decidim/only_forms/test/factories.rb +13 -0
- data/lib/decidim/only_forms/version.rb +14 -0
- data/lib/decidim/only_forms.rb +13 -0
- metadata +143 -0
data/README.md
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
<h1 align="center"><img src="https://github.com/octree-gva/meta/blob/main/decidim/static/header.png?raw=true" alt="Decidim - Octree Participatory democracy on a robust and open source solution" /></h1>
|
2
|
+
<h4 align="center">
|
3
|
+
<a href="https://www.octree.ch">Octree</a> |
|
4
|
+
<a href="https://octree.ch/en/contact-us/">Contact Us</a> |
|
5
|
+
<a href="https://blog.octree.ch">Our Blog (FR)</a><br/><br/>
|
6
|
+
<a href="https://decidim.org">Decidim</a> |
|
7
|
+
<a href="https://docs.decidim.org/en/">Decidim Docs</a> |
|
8
|
+
<a href="https://meta.decidim.org">Participatory Governance (meta decidim)</a><br/><br/>
|
9
|
+
<a href="https://matrix.to/#/+decidim:matrix.org">Decidim Community (Matrix+Element.io)</a>
|
10
|
+
</h4>
|
11
|
+
<p align="center">
|
12
|
+
<a href="https://mkutano.community"><img src="https://github.com/octree-gva/decidim-module-referral/blob/main/mkutano-logo.png?raw=true" alt="MKUTANO is a participatory platform where black canadians can effectively & democratically organize at scale" /></a>
|
13
|
+
</p>
|
14
|
+
|
15
|
+
# Decidim::OnlyForms
|
16
|
+
|
17
|
+
Component to create forms in a participatory space, sponsored by the [Mkutano Community](mkutano.community).
|
18
|
+
|
19
|
+
## Usage
|
20
|
+
|
21
|
+
OnlyForms will be available as a Component for a Participatory Space.
|
22
|
+
|
23
|
+
## Installation
|
24
|
+
|
25
|
+
Add this line to your application's Gemfile:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
gem "decidim-only_forms"
|
29
|
+
```
|
30
|
+
|
31
|
+
And then execute:
|
32
|
+
|
33
|
+
```bash
|
34
|
+
bundle
|
35
|
+
bundle exec rails decidim_only_forms:install:migrations
|
36
|
+
bundle exec rails db:migrate
|
37
|
+
```
|
38
|
+
|
39
|
+
## Testing
|
40
|
+
```
|
41
|
+
bundle exec rake test_app
|
42
|
+
```
|
43
|
+
|
44
|
+
## Local development
|
45
|
+
For decidim version 0.27, use Gemfile.0.27. For version 0.26, use Gemfile.0.26
|
46
|
+
```
|
47
|
+
cp Gemfile.0.27 Gemfile
|
48
|
+
```
|
49
|
+
|
50
|
+
First, you need to run an empty database with a decidim dev container which runs nothing.
|
51
|
+
```
|
52
|
+
docker-compose down -v --remove-orphans
|
53
|
+
docker-compose up -d
|
54
|
+
```
|
55
|
+
|
56
|
+
Once created, you access the decidim container
|
57
|
+
```
|
58
|
+
# Get the id of the decidim dev container
|
59
|
+
docker ps --format {{.ID}} --filter=label=org.label-schema.name=decidim
|
60
|
+
# 841ae977c7da
|
61
|
+
docker exec -it 841ae977c7da bash
|
62
|
+
```
|
63
|
+
You are now in bash, run manually. This will check your environment and do migrations if needed
|
64
|
+
```
|
65
|
+
docker-entrypoint
|
66
|
+
```
|
67
|
+
|
68
|
+
You are now ready to use your container in the way you want for development:
|
69
|
+
|
70
|
+
* Run a rails seed: `bundle exec rails db:seed`
|
71
|
+
* Have live-reload on your assets: `bin/webpack-dev-server`
|
72
|
+
* Execute tasks, like `bundle exec rails g migration AddSomeColumn`
|
73
|
+
* Run the rails server: `bundle exec rails s -b 0.0.0.0`
|
74
|
+
* etc.
|
75
|
+
|
76
|
+
To stop everything, uses:
|
77
|
+
- `docker-compose down` to stop the containers
|
78
|
+
- `docker-compose down -v` to stop the containers and remove all previously saved data.
|
79
|
+
|
80
|
+
### Debugging
|
81
|
+
To debug something on the container:
|
82
|
+
1. Ensure `decidim-app` is running
|
83
|
+
```bash
|
84
|
+
docker ps --all
|
85
|
+
# CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
|
86
|
+
# 915e9fc474f2 decidim-module-only_forms-decidim-app "sleep infinity" 7 hours ago Up 7 hours 0.0.0.0:3000->3000/tcp, :::3000->3000/tcp, 0.0.0.0:3035->3035/tcp, :::3035->3035/tcp decidim-only-form-app
|
87
|
+
# 22304921b7eb postgres:14-alpine "docker-entrypoint.s…" 7 hours ago Up 7 hours (healthy) 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp decidim-module-only_forms-pg-1
|
88
|
+
|
89
|
+
```
|
90
|
+
|
91
|
+
2. In another terminal, run `docker exec -it 915e9fc474f2 bash`
|
92
|
+
3. Run
|
93
|
+
- `tail -f $ROOT/log/development.log` to **access logs**
|
94
|
+
- `bundle exec rails restart` to **restart rails server AND keeps webpacker running**
|
95
|
+
- `cd $ROOT` to access the `development_app`
|
96
|
+
- `cd $ROOT/../decidim_module_only_forms` to access the module directory
|
97
|
+
## Contributing
|
98
|
+
|
99
|
+
See [Decidim](https://github.com/decidim/decidim).
|
100
|
+
|
101
|
+
## License
|
102
|
+
|
103
|
+
This engine is distributed under the [GNU AFFERO GENERAL PUBLIC LICENSE](LICENSE-AGPLv3.txt)
|
104
|
+
|
105
|
+
<br /><br />
|
106
|
+
<p align="center">
|
107
|
+
<img src="https://raw.githubusercontent.com/octree-gva/meta/main/decidim/static/octree_and_decidim.png" height="90" alt="Decidim Installation by Octree" />
|
108
|
+
</p>
|
data/Rakefile
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "decidim/dev/common_rake"
|
4
|
+
|
5
|
+
def install_module(path)
|
6
|
+
Dir.chdir(path) do
|
7
|
+
# system("bundle exec rake decidim_vocacity_gem_tasks:install:migrations")
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def seed_db(path)
|
12
|
+
Dir.chdir(path) do
|
13
|
+
system("bundle exec rake db:seed")
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Prepare for testing"
|
18
|
+
task :prepare_tests do
|
19
|
+
system("bundle add doorkeeper")
|
20
|
+
system("bundle exec rails generate doorkeeper:install")
|
21
|
+
system("bundle exec rails generate doorkeeper:migration")
|
22
|
+
# Remove previous existing db, and recreate one.
|
23
|
+
disable_docker_compose = ENV.fetch("DISABLED_DOCKER_COMPOSE", "false") == "true"
|
24
|
+
unless disable_docker_compose
|
25
|
+
system("sudo docker-compose down -v")
|
26
|
+
system("sudo docker-compose up -d --remove-orphans")
|
27
|
+
end
|
28
|
+
ENV["RAILS_ENV"] = "test"
|
29
|
+
databaseYml = {
|
30
|
+
"test" => {
|
31
|
+
"adapter" => "postgresql",
|
32
|
+
"encoding" => "unicode",
|
33
|
+
"host" => ENV.fetch("DATABASE_HOST", "localhost"),
|
34
|
+
"port" => ENV.fetch("DATABASE_PORT", "5432").to_i,
|
35
|
+
"username" => ENV.fetch("DATABASE_USERNAME", "decidim"),
|
36
|
+
"password" => ENV.fetch("DATABASE_PASSWORD", "TEST-baeGhi4Ohtahcee5eejoaxaiwaezaiGo"),
|
37
|
+
"database" => "decidim_test"
|
38
|
+
}
|
39
|
+
}
|
40
|
+
config_file = File.expand_path("spec/dummy/config/database.yml", __dir__)
|
41
|
+
File.open(config_file, "w") { |f| YAML.dump(databaseYml, f) }
|
42
|
+
Dir.chdir("spec/dummy") do
|
43
|
+
system("bundle exec rails db:migrate")
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
desc "Generates a dummy app for testing"
|
48
|
+
task :test_app do
|
49
|
+
Bundler.with_original_env do
|
50
|
+
generate_decidim_app(
|
51
|
+
"spec/dummy",
|
52
|
+
"--app_name",
|
53
|
+
"decidim_test",
|
54
|
+
"--path",
|
55
|
+
"../..",
|
56
|
+
"--skip_spring",
|
57
|
+
"--demo",
|
58
|
+
"--force_ssl",
|
59
|
+
"false",
|
60
|
+
"--locales",
|
61
|
+
"en,fr,es"
|
62
|
+
)
|
63
|
+
end
|
64
|
+
install_module("spec/dummy")
|
65
|
+
Rake::Task["prepare_tests"].invoke
|
66
|
+
end
|
67
|
+
|
68
|
+
desc "Generates a development app"
|
69
|
+
task :development_app do
|
70
|
+
Bundler.with_original_env do
|
71
|
+
generate_decidim_app(
|
72
|
+
"development_app",
|
73
|
+
"--app_name",
|
74
|
+
"#{base_app_name}_development_app",
|
75
|
+
"--path",
|
76
|
+
"..",
|
77
|
+
"--recreate_db",
|
78
|
+
"--demo"
|
79
|
+
)
|
80
|
+
end
|
81
|
+
|
82
|
+
system("bin/rails generate doorkeeper:install")
|
83
|
+
system("bin/rails generate doorkeeper:migration")
|
84
|
+
|
85
|
+
install_module("development_app")
|
86
|
+
seed_db("development_app")
|
87
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
# This command is executed when the user answers a Questionnaire.
|
6
|
+
class AnswerQuestionnaire < Decidim::Forms::Command
|
7
|
+
include ::Decidim::MultipleAttachmentsMethods
|
8
|
+
|
9
|
+
# Initializes a AnswerQuestionnaire Command.
|
10
|
+
#
|
11
|
+
# form - The form from which to get the data.
|
12
|
+
# questionnaire - The current instance of the questionnaire to be answered.
|
13
|
+
def initialize(form, current_user, questionnaire)
|
14
|
+
@form = form
|
15
|
+
@current_user = current_user
|
16
|
+
@questionnaire = questionnaire
|
17
|
+
end
|
18
|
+
|
19
|
+
# Answers a questionnaire if it is valid
|
20
|
+
#
|
21
|
+
# Broadcasts :ok if successful, :invalid otherwise.
|
22
|
+
def call
|
23
|
+
return broadcast(:invalid) if @form.invalid? || user_already_answered?
|
24
|
+
|
25
|
+
answer_questionnaire
|
26
|
+
|
27
|
+
if @errors
|
28
|
+
reset_form_attachments
|
29
|
+
broadcast(:invalid)
|
30
|
+
else
|
31
|
+
broadcast(:ok)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
attr_reader :form, :questionnaire, :current_user
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
# This method will add an error to the `add_documents` field only if there's
|
40
|
+
# any error in any other field or an error in another answer in the
|
41
|
+
# questionnaire. This is needed because when the form has
|
42
|
+
# an error, the attachments are lost, so we need a way to inform the user
|
43
|
+
# of this problem.
|
44
|
+
def reset_form_attachments
|
45
|
+
@form.responses.each do |answer|
|
46
|
+
answer.errors.add(:add_documents, :needs_to_be_reattached) if answer.has_attachments? || answer.has_error_in_attachments?
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def answer_questionnaire
|
51
|
+
@main_form = @form
|
52
|
+
@errors = nil
|
53
|
+
|
54
|
+
Answer.transaction(requires_new: true) do
|
55
|
+
form.responses_by_step.flatten.select(&:display_conditions_fulfilled?).each do |form_answer|
|
56
|
+
answer = Answer.new(
|
57
|
+
user: @current_user,
|
58
|
+
questionnaire: @questionnaire,
|
59
|
+
question: form_answer.question,
|
60
|
+
body: form_answer.body,
|
61
|
+
session_token: form.context.session_token,
|
62
|
+
ip_hash: form.context.ip_hash
|
63
|
+
)
|
64
|
+
|
65
|
+
form_answer.selected_choices.each do |choice|
|
66
|
+
answer.choices.build(
|
67
|
+
body: choice.body,
|
68
|
+
custom_body: choice.custom_body,
|
69
|
+
decidim_answer_option_id: choice.answer_option_id,
|
70
|
+
decidim_question_matrix_row_id: choice.matrix_row_id,
|
71
|
+
position: choice.position
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
answer.save!
|
76
|
+
|
77
|
+
next unless form_answer.question.has_attachments?
|
78
|
+
|
79
|
+
# The attachments module expects `@form` to be the form with the
|
80
|
+
# attachments
|
81
|
+
@form = form_answer
|
82
|
+
@attached_to = answer
|
83
|
+
|
84
|
+
build_attachments
|
85
|
+
|
86
|
+
if attachments_invalid?
|
87
|
+
@errors = true
|
88
|
+
next
|
89
|
+
end
|
90
|
+
|
91
|
+
create_attachments if process_attachments?
|
92
|
+
document_cleanup!
|
93
|
+
end
|
94
|
+
|
95
|
+
@form = @main_form
|
96
|
+
raise ActiveRecord::Rollback if @errors
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def user_already_answered?
|
101
|
+
return false if allow_multiple_answers?
|
102
|
+
|
103
|
+
questionnaire.answered_by?(current_user || form.context.session_token)
|
104
|
+
end
|
105
|
+
|
106
|
+
def allow_multiple_answers?
|
107
|
+
return current_settings.allow_multiple_answers if current_settings.respond_to?("allow_multiple_answers")
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
# maintains compatibility with v0.26 which uses Rectify
|
6
|
+
if defined? ::Decidim::Command
|
7
|
+
class Command < ::Decidim::Command
|
8
|
+
end
|
9
|
+
else
|
10
|
+
class Command < ::Rectify::Command
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,154 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Forms
|
5
|
+
module Concerns
|
6
|
+
# Questionnaires can be related to any class in Decidim, in order to
|
7
|
+
# manage the questionnaires for a given type, you should create a new
|
8
|
+
# controller and include this concern.
|
9
|
+
#
|
10
|
+
# The only requirement is to define a `questionnaire_for` method that
|
11
|
+
# returns an instance of the model that questionnaire belongs to.
|
12
|
+
module HasQuestionnaire
|
13
|
+
extend ActiveSupport::Concern
|
14
|
+
|
15
|
+
included do
|
16
|
+
helper Decidim::Forms::ApplicationHelper
|
17
|
+
include FormFactory
|
18
|
+
|
19
|
+
helper_method :questionnaire_for, :questionnaire, :allow_answers?, :visitor_can_answer?, :visitor_already_answered?, :update_url, :form_path
|
20
|
+
|
21
|
+
invisible_captcha on_spam: :spam_detected
|
22
|
+
|
23
|
+
def show
|
24
|
+
@form = form(Decidim::Forms::QuestionnaireForm).from_model(questionnaire)
|
25
|
+
render template: "decidim/forms/questionnaires/show"
|
26
|
+
end
|
27
|
+
|
28
|
+
def answer
|
29
|
+
enforce_permission_to_answer_questionnaire
|
30
|
+
|
31
|
+
@form = form(Decidim::Forms::QuestionnaireForm).from_params(params, session_token: session_token, ip_hash: ip_hash)
|
32
|
+
Decidim::Forms::AnswerQuestionnaire.call(@form, current_user, questionnaire) do
|
33
|
+
on(:ok) do
|
34
|
+
# i18n-tasks-use t("decidim.forms.questionnaires.answer.success")
|
35
|
+
flash[:notice] = I18n.t("answer.success", scope: i18n_flashes_scope)
|
36
|
+
redirect_to after_answer_path
|
37
|
+
end
|
38
|
+
|
39
|
+
on(:invalid) do
|
40
|
+
# i18n-tasks-use t("decidim.forms.questionnaires.answer.invalid")
|
41
|
+
flash.now[:alert] = I18n.t("answer.invalid", scope: i18n_flashes_scope)
|
42
|
+
render template: "decidim/forms/questionnaires/show"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Method to be implemented at the controller. You need to
|
48
|
+
# return true if the questionnaire can receive answers
|
49
|
+
def allow_answers?
|
50
|
+
raise "#{self.class.name} is expected to implement #allow_answers?"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Public: Method to be implemented at the controller if needed. You need to
|
54
|
+
# return true if the questionnaire can receive answers by unregistered users
|
55
|
+
def allow_unregistered?
|
56
|
+
false
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public: return true if the current user (if logged) can answer the questionnaire
|
60
|
+
def visitor_can_answer?
|
61
|
+
current_user || allow_unregistered?
|
62
|
+
end
|
63
|
+
|
64
|
+
# Public: return false if survey allow multiple answers
|
65
|
+
# if not, return true if the current user (or session visitor) already answered the questionnaire
|
66
|
+
def visitor_already_answered?
|
67
|
+
return false if allow_multiple_answers?
|
68
|
+
|
69
|
+
questionnaire.answered_by?(current_user || tokenize(session[:session_id]))
|
70
|
+
end
|
71
|
+
|
72
|
+
# Public: Returns a String or Object that will be passed to `redirect_to` after
|
73
|
+
# answering the questionnaire. By default it redirects to the questionnaire_for.
|
74
|
+
#
|
75
|
+
# It can be redefined at controller level if you need to redirect elsewhere.
|
76
|
+
def after_answer_path
|
77
|
+
questionnaire_for
|
78
|
+
end
|
79
|
+
|
80
|
+
# You can implement this method in your controller to change the URL
|
81
|
+
# where the questionnaire will be submitted.
|
82
|
+
def update_url
|
83
|
+
url_for([questionnaire_for, { action: :answer }])
|
84
|
+
end
|
85
|
+
|
86
|
+
# Points to the shortest path accessing the current form. This will be
|
87
|
+
# used to detect whether a user is leaving the form with some partial
|
88
|
+
# answers, so that we can warn them.
|
89
|
+
#
|
90
|
+
# Overwrite this method at the controller.
|
91
|
+
def form_path
|
92
|
+
url_for([questionnaire_for, { only_path: true }])
|
93
|
+
end
|
94
|
+
|
95
|
+
# Public: Method to be implemented at the controller. You need to
|
96
|
+
# return the object that will hold the questionnaire.
|
97
|
+
def questionnaire_for
|
98
|
+
raise "#{self.class.name} is expected to implement #questionnaire_for"
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def allow_multiple_answers?
|
104
|
+
return current_settings.allow_multiple_answers if current_settings.respond_to?("allow_multiple_answers")
|
105
|
+
end
|
106
|
+
|
107
|
+
def i18n_flashes_scope
|
108
|
+
"decidim.forms.questionnaires"
|
109
|
+
end
|
110
|
+
|
111
|
+
def questionnaire
|
112
|
+
@questionnaire ||= Questionnaire.includes(questions: :answer_options).find_by(questionnaire_for: questionnaire_for)
|
113
|
+
end
|
114
|
+
|
115
|
+
def spam_detected
|
116
|
+
enforce_permission_to_answer_questionnaire
|
117
|
+
|
118
|
+
@form = form(Decidim::Forms::QuestionnaireForm).from_params(params)
|
119
|
+
|
120
|
+
flash.now[:alert] = I18n.t("answer.spam_detected", scope: i18n_flashes_scope)
|
121
|
+
render template: "decidim/forms/questionnaires/show"
|
122
|
+
end
|
123
|
+
|
124
|
+
# You can implement this method in your controller to change the
|
125
|
+
# enforce_permission_to arguments.
|
126
|
+
def enforce_permission_to_answer_questionnaire
|
127
|
+
enforce_permission_to :answer, :questionnaire
|
128
|
+
end
|
129
|
+
|
130
|
+
def ip_hash
|
131
|
+
return nil unless request&.remote_ip
|
132
|
+
|
133
|
+
@ip_hash ||= tokenize(request&.remote_ip)
|
134
|
+
end
|
135
|
+
|
136
|
+
# token is used as a substitute of user_id if unregistered
|
137
|
+
def session_token
|
138
|
+
id = current_user&.id
|
139
|
+
session_id = request.session[:session_id] if request&.session
|
140
|
+
|
141
|
+
return nil unless id || session_id
|
142
|
+
|
143
|
+
@session_token ||= tokenize(id || session_id)
|
144
|
+
end
|
145
|
+
|
146
|
+
def tokenize(id, length: 10)
|
147
|
+
tokenizer = Decidim::Tokenizer.new(salt: questionnaire.salt || questionnaire.id, length: length)
|
148
|
+
tokenizer.int_digest(id).to_s
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module Surveys
|
5
|
+
# The data store for a Survey in the Decidim::Surveys component.
|
6
|
+
class Survey < Surveys::ApplicationRecord
|
7
|
+
include Decidim::Resourceable
|
8
|
+
include Decidim::Forms::HasQuestionnaire
|
9
|
+
include Decidim::HasComponent
|
10
|
+
|
11
|
+
#component_manifest_name "surveys"
|
12
|
+
|
13
|
+
delegate :title, to: :questionnaire
|
14
|
+
|
15
|
+
validates :questionnaire, presence: true
|
16
|
+
|
17
|
+
def clean_after_publish?
|
18
|
+
component.settings.clean_after_publish?
|
19
|
+
end
|
20
|
+
|
21
|
+
def starts_at
|
22
|
+
component.settings.starts_at
|
23
|
+
end
|
24
|
+
|
25
|
+
def ends_at
|
26
|
+
component.settings.ends_at
|
27
|
+
end
|
28
|
+
|
29
|
+
def open?
|
30
|
+
return true if starts_at.blank? && ends_at.blank?
|
31
|
+
return true if ends_at.blank? && starts_at.past?
|
32
|
+
return true if starts_at.blank? && ends_at.future?
|
33
|
+
|
34
|
+
return Time.zone.now.between?(starts_at, ends_at) if starts_at.present? && ends_at.present?
|
35
|
+
|
36
|
+
false
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
---
|
2
|
+
en:
|
3
|
+
decidim:
|
4
|
+
components:
|
5
|
+
only_forms:
|
6
|
+
name: Forms
|
7
|
+
settings:
|
8
|
+
global:
|
9
|
+
clean_after_publish: Delete answers when publishing the form
|
10
|
+
ends_at: Answers accepted until
|
11
|
+
ends_at_help: Leave blank for no specific date
|
12
|
+
scope_id: Scope
|
13
|
+
scopes_enabled: Scopes enabled
|
14
|
+
starts_at: Answers accepted from
|
15
|
+
starts_at_help: Leave blank for no specific date
|
16
|
+
announcement: Announcement
|
17
|
+
step:
|
18
|
+
allow_multiple_answers: Allow Multiple Answers
|
19
|
+
allow_answers: Allow answers
|
20
|
+
allow_unregistered: Allow unregistered users to answer the form
|
21
|
+
allow_unregistered_help: If active, no login will be required in order to answer the form. This may lead to poor or unreliable data and it will be more vulnerable to automated attacks. Use with caution!
|
22
|
+
announcement: Announcement
|
23
|
+
only_forms:
|
24
|
+
admin:
|
25
|
+
exports:
|
26
|
+
survey_user_answers: Forms participant answers
|
27
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
---
|
2
|
+
fr:
|
3
|
+
decidim:
|
4
|
+
components:
|
5
|
+
only_forms:
|
6
|
+
name: Formulaire
|
7
|
+
settings:
|
8
|
+
global:
|
9
|
+
clean_after_publish: Supprimer les réponses après publication du formulaire
|
10
|
+
ends_at: Réponses acceptées jusqu'à
|
11
|
+
ends_at_help: Laisser vide si pas de limite de date pour recevoir des réponses
|
12
|
+
scope_id: Scope
|
13
|
+
scopes_enabled: Scopes enabled
|
14
|
+
starts_at: Réponses acceptées depuis
|
15
|
+
starts_at_help: Laisser vide si pas de limite de date pour débuter la réception des réponses
|
16
|
+
announcement: Annonces
|
17
|
+
step:
|
18
|
+
allow_multiple_answers: Autoriser les utilisateurs à répondre plusieurs fois
|
19
|
+
allow_answers: Autoriser les réponses
|
20
|
+
allow_unregistered: Autoriser les réponses pour les visiteurs
|
21
|
+
allow_unregistered_help: Si ceci est actif, les visiteurs n'auront pas besoin de compte pour répondre.
|
22
|
+
announcement: Annonce
|
23
|
+
only_forms:
|
24
|
+
admin:
|
25
|
+
exports:
|
26
|
+
survey_user_answers: Réponses au formulaire
|
27
|
+
|
@@ -0,0 +1,27 @@
|
|
1
|
+
---
|
2
|
+
pt:
|
3
|
+
decidim:
|
4
|
+
components:
|
5
|
+
only_forms:
|
6
|
+
name: Formulario
|
7
|
+
settings:
|
8
|
+
global:
|
9
|
+
clean_after_publish: Deletar as respostas apos publicação
|
10
|
+
ends_at: Respostas aceitas até
|
11
|
+
ends_at_help: Deixa vazia se não tem limitas para receber respostas
|
12
|
+
scope_id: Scope
|
13
|
+
scopes_enabled: Scopes enabled
|
14
|
+
starts_at: Responstas ativas depois do
|
15
|
+
starts_at_help: Deixa vazia se não tem inicio para receber respostas
|
16
|
+
announcement: Anuncios
|
17
|
+
step:
|
18
|
+
allow_multiple_answers: Deixar os usarios responder mais que uma vez
|
19
|
+
allow_answers: Autorisar respostas
|
20
|
+
allow_unregistered: Autorisar respostas de visitores
|
21
|
+
allow_unregistered_help: Se isso esta ativos, pessoas sem contas poderam responder.
|
22
|
+
announcement: Anuncios
|
23
|
+
only_forms:
|
24
|
+
admin:
|
25
|
+
exports:
|
26
|
+
survey_user_answers: Responsas ao formulario
|
27
|
+
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Decidim
|
4
|
+
module OnlyForms
|
5
|
+
# This is the engine that runs on the public interface of `OnlyForms`.
|
6
|
+
class AdminEngine < ::Rails::Engine
|
7
|
+
isolate_namespace Decidim::OnlyForms::Admin
|
8
|
+
|
9
|
+
paths["db/migrate"] = nil
|
10
|
+
paths["lib/tasks"] = nil
|
11
|
+
|
12
|
+
routes do
|
13
|
+
# Add admin engine routes here
|
14
|
+
end
|
15
|
+
|
16
|
+
def load_seed
|
17
|
+
nil
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_dependency "decidim/components/namer"
|
4
|
+
|
5
|
+
Decidim.register_component(:only_forms) do |component|
|
6
|
+
survey_component = Decidim.find_component_manifest("surveys")
|
7
|
+
component.attributes = survey_component.attributes.deep_dup
|
8
|
+
component.name = "only_forms"
|
9
|
+
|
10
|
+
component.settings(:global) do |settings|
|
11
|
+
settings.attribute :scopes_enabled, type: :boolean, default: false
|
12
|
+
settings.attribute :scope_id, type: :scope
|
13
|
+
settings.attribute :starts_at, type: :time
|
14
|
+
settings.attribute :ends_at, type: :time
|
15
|
+
settings.attribute :announcement, type: :text, translated: true, editor: true
|
16
|
+
settings.attribute :clean_after_publish, type: :boolean, default: true
|
17
|
+
end
|
18
|
+
|
19
|
+
component.settings(:step) do |settings|
|
20
|
+
settings.attribute :allow_answers, type: :boolean, default: false
|
21
|
+
settings.attribute :allow_unregistered, type: :boolean, default: false
|
22
|
+
settings.attribute :allow_multiple_answers, type: :boolean, default: true
|
23
|
+
settings.attribute :announcement, type: :text, translated: true, editor: true
|
24
|
+
end
|
25
|
+
|
26
|
+
component.exports :survey_user_answers do |exports|
|
27
|
+
exports.collection do |f|
|
28
|
+
survey = Decidim::Surveys::Survey.find_by(component: f)
|
29
|
+
Decidim::Forms::QuestionnaireUserAnswers.for(survey.questionnaire)
|
30
|
+
end
|
31
|
+
|
32
|
+
exports.formats %w(CSV JSON Excel)
|
33
|
+
|
34
|
+
exports.serializer Decidim::Forms::UserAnswersSerializer
|
35
|
+
end
|
36
|
+
|
37
|
+
component.register_resource(:survey) do |resource|
|
38
|
+
resource.model_class_name = "Decidim::Surveys::Survey"
|
39
|
+
end
|
40
|
+
component
|
41
|
+
end
|