Add polls plugin
This commit is contained in:
parent
42e0ce86a8
commit
d476993321
29 changed files with 787 additions and 0 deletions
1
Gemfile
1
Gemfile
|
@ -61,6 +61,7 @@ gem 'foodsoft_wiki', path: 'plugins/wiki'
|
||||||
gem 'foodsoft_messages', path: 'plugins/messages'
|
gem 'foodsoft_messages', path: 'plugins/messages'
|
||||||
gem 'foodsoft_documents', path: 'plugins/documents'
|
gem 'foodsoft_documents', path: 'plugins/documents'
|
||||||
gem 'foodsoft_discourse', path: 'plugins/discourse'
|
gem 'foodsoft_discourse', path: 'plugins/discourse'
|
||||||
|
gem 'foodsoft_polls', path: 'plugins/polls'
|
||||||
|
|
||||||
# plugins not enabled by default
|
# plugins not enabled by default
|
||||||
#gem 'foodsoft_current_orders', path: 'plugins/current_orders'
|
#gem 'foodsoft_current_orders', path: 'plugins/current_orders'
|
||||||
|
|
|
@ -36,6 +36,13 @@ PATH
|
||||||
mail
|
mail
|
||||||
rails
|
rails
|
||||||
|
|
||||||
|
PATH
|
||||||
|
remote: plugins/polls
|
||||||
|
specs:
|
||||||
|
foodsoft_polls (0.0.1)
|
||||||
|
deface (~> 1.0)
|
||||||
|
rails
|
||||||
|
|
||||||
PATH
|
PATH
|
||||||
remote: plugins/wiki
|
remote: plugins/wiki
|
||||||
specs:
|
specs:
|
||||||
|
@ -528,6 +535,7 @@ DEPENDENCIES
|
||||||
foodsoft_discourse!
|
foodsoft_discourse!
|
||||||
foodsoft_documents!
|
foodsoft_documents!
|
||||||
foodsoft_messages!
|
foodsoft_messages!
|
||||||
|
foodsoft_polls!
|
||||||
foodsoft_wiki!
|
foodsoft_wiki!
|
||||||
gaffe
|
gaffe
|
||||||
haml (~> 4.0)
|
haml (~> 4.0)
|
||||||
|
|
|
@ -210,6 +210,12 @@ class User < ApplicationRecord
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def self.custom_fields
|
||||||
|
fields = FoodsoftConfig[:custom_fields] && FoodsoftConfig[:custom_fields][:user]
|
||||||
|
return [] unless fields
|
||||||
|
fields.map(&:deep_symbolize_keys)
|
||||||
|
end
|
||||||
|
|
||||||
# XXX this is view-related; need to move out things like token_attributes
|
# XXX this is view-related; need to move out things like token_attributes
|
||||||
# then this can be removed
|
# then this can be removed
|
||||||
def display
|
def display
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
class CreatePolls < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :polls do |t|
|
||||||
|
t.integer :created_by_user_id, null: false
|
||||||
|
t.string :name, null: false
|
||||||
|
t.text :description
|
||||||
|
t.datetime :starts
|
||||||
|
t.datetime :ends
|
||||||
|
t.boolean :one_vote_per_ordergroup, default: false, null: false
|
||||||
|
t.text :required_ordergroup_custom_fields
|
||||||
|
t.text :required_user_custom_fields
|
||||||
|
t.integer :voting_method, null: false
|
||||||
|
t.string :choices, array: true, null: false
|
||||||
|
t.integer :final_choice, index: true
|
||||||
|
t.integer :multi_select_count, default: 0, null: false
|
||||||
|
t.integer :min_points
|
||||||
|
t.integer :max_points
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :poll_votes do |t|
|
||||||
|
t.references :poll, null: false
|
||||||
|
t.references :user, null: false
|
||||||
|
t.references :ordergroup
|
||||||
|
t.text :note
|
||||||
|
t.timestamps
|
||||||
|
t.index [:poll_id, :user_id, :ordergroup_id], unique: true
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :poll_choices do |t|
|
||||||
|
t.references :poll_vote, null: false
|
||||||
|
t.integer :choice, null: false
|
||||||
|
t.integer :value, null: false
|
||||||
|
t.index [:poll_vote_id, :choice], unique: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
40
db/schema.rb
40
db/schema.rb
|
@ -389,6 +389,46 @@ ActiveRecord::Schema.define(version: 20181201000200) do
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "poll_choices", force: :cascade do |t|
|
||||||
|
t.integer "poll_vote_id", limit: 4, null: false
|
||||||
|
t.integer "choice", limit: 4, null: false
|
||||||
|
t.integer "value", limit: 4, null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "poll_choices", ["poll_vote_id", "choice"], name: "index_poll_choices_on_poll_vote_id_and_choice", unique: true, using: :btree
|
||||||
|
|
||||||
|
create_table "poll_votes", force: :cascade do |t|
|
||||||
|
t.integer "poll_id", limit: 4, null: false
|
||||||
|
t.integer "user_id", limit: 4, null: false
|
||||||
|
t.integer "ordergroup_id", limit: 4
|
||||||
|
t.text "note", limit: 65535
|
||||||
|
t.datetime "created_at"
|
||||||
|
t.datetime "updated_at"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "poll_votes", ["poll_id", "user_id", "ordergroup_id"], name: "index_poll_votes_on_poll_id_and_user_id_and_ordergroup_id", unique: true, using: :btree
|
||||||
|
|
||||||
|
create_table "polls", force: :cascade do |t|
|
||||||
|
t.integer "created_by_user_id", limit: 4, null: false
|
||||||
|
t.string "name", limit: 255, null: false
|
||||||
|
t.text "description", limit: 65535
|
||||||
|
t.datetime "starts"
|
||||||
|
t.datetime "ends"
|
||||||
|
t.boolean "one_vote_per_ordergroup", default: false, null: false
|
||||||
|
t.text "required_ordergroup_custom_fields", limit: 65535
|
||||||
|
t.text "required_user_custom_fields", limit: 65535
|
||||||
|
t.integer "voting_method", limit: 4, null: false
|
||||||
|
t.string "choices", limit: 255, null: false
|
||||||
|
t.integer "final_choice", limit: 4
|
||||||
|
t.integer "multi_select_count", limit: 4, default: 0, null: false
|
||||||
|
t.integer "min_points", limit: 4
|
||||||
|
t.integer "max_points", limit: 4
|
||||||
|
t.datetime "created_at"
|
||||||
|
t.datetime "updated_at"
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "polls", ["final_choice"], name: "index_polls_on_final_choice", using: :btree
|
||||||
|
|
||||||
create_table "printer_job_updates", force: :cascade do |t|
|
create_table "printer_job_updates", force: :cascade do |t|
|
||||||
t.integer "printer_job_id", limit: 4, null: false
|
t.integer "printer_job_id", limit: 4, null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
|
|
18
plugins/polls/README.md
Normal file
18
plugins/polls/README.md
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
FoodsoftPolls
|
||||||
|
=============
|
||||||
|
|
||||||
|
This plugin adds polls to foodsoft. A new 'Polls' menu entry is added below the 'Foodcoops' menu in the navigation bar.
|
||||||
|
|
||||||
|
This plugin is enabled by default in foodsoft, so you don't need to do anything
|
||||||
|
to install it. If you still want to, for example when it has been disabled,
|
||||||
|
add the following to foodsoft's Gemfile:
|
||||||
|
|
||||||
|
```Gemfile
|
||||||
|
gem 'foodsoft_polls', path: 'lib/foodsoft_polls'
|
||||||
|
```
|
||||||
|
|
||||||
|
This plugin introduces the foodcoop config option `use_polls`, which can be
|
||||||
|
set to `false` to disable polls. May be useful in multicoop deployments.
|
||||||
|
|
||||||
|
This plugin is part of the foodsoft package and uses the AGPL-3 license (see
|
||||||
|
foodsoft's LICENSE for the full license text).
|
40
plugins/polls/Rakefile
Normal file
40
plugins/polls/Rakefile
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
#!/usr/bin/env rake
|
||||||
|
begin
|
||||||
|
require 'bundler/setup'
|
||||||
|
rescue LoadError
|
||||||
|
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
||||||
|
end
|
||||||
|
begin
|
||||||
|
require 'rdoc/task'
|
||||||
|
rescue LoadError
|
||||||
|
require 'rdoc/rdoc'
|
||||||
|
require 'rake/rdoctask'
|
||||||
|
RDoc::Task = Rake::RDocTask
|
||||||
|
end
|
||||||
|
|
||||||
|
RDoc::Task.new(:rdoc) do |rdoc|
|
||||||
|
rdoc.rdoc_dir = 'rdoc'
|
||||||
|
rdoc.title = 'FoodsoftDocuments'
|
||||||
|
rdoc.options << '--line-numbers'
|
||||||
|
rdoc.rdoc_files.include('README.rdoc')
|
||||||
|
rdoc.rdoc_files.include('lib/**/*.rb')
|
||||||
|
end
|
||||||
|
|
||||||
|
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
||||||
|
load 'rails/tasks/engine.rake'
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Bundler::GemHelper.install_tasks
|
||||||
|
|
||||||
|
require 'rake/testtask'
|
||||||
|
|
||||||
|
Rake::TestTask.new(:test) do |t|
|
||||||
|
t.libs << 'lib'
|
||||||
|
t.libs << 'test'
|
||||||
|
t.pattern = 'test/**/*_test.rb'
|
||||||
|
t.verbose = false
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
task :default => :test
|
108
plugins/polls/app/controllers/polls_controller.rb
Normal file
108
plugins/polls/app/controllers/polls_controller.rb
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
class PollsController < ApplicationController
|
||||||
|
before_filter -> { require_plugin_enabled FoodsoftPolls }
|
||||||
|
|
||||||
|
def index
|
||||||
|
@polls = Poll.page(params[:page]).per(@per_page).order(created_at: :desc)
|
||||||
|
end
|
||||||
|
|
||||||
|
def show
|
||||||
|
@poll = Poll.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def new
|
||||||
|
@poll = Poll.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@poll = Poll.new(poll_params)
|
||||||
|
@poll.created_by = current_user
|
||||||
|
|
||||||
|
if @poll.save
|
||||||
|
redirect_to @poll, notice: t('.notice')
|
||||||
|
else
|
||||||
|
render action: 'edit'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
@poll = Poll.find(params[:id])
|
||||||
|
|
||||||
|
if user_has_no_right
|
||||||
|
redirect_to polls_path, alert: t('.no_right')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@poll = Poll.find(params[:id])
|
||||||
|
|
||||||
|
if user_has_no_right
|
||||||
|
redirect_to polls_path, alert: t('.no_right')
|
||||||
|
elsif @poll.update_attributes(poll_params)
|
||||||
|
redirect_to @poll, notice: t('.notice')
|
||||||
|
else
|
||||||
|
render action: 'edit'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@poll = Poll.find(params[:id])
|
||||||
|
|
||||||
|
if user_has_no_right
|
||||||
|
redirect_to polls_path, alert: t('.no_right')
|
||||||
|
else
|
||||||
|
@poll.destroy
|
||||||
|
redirect_to polls_path, notice: t('.notice')
|
||||||
|
end
|
||||||
|
rescue => error
|
||||||
|
redirect_to polls_path, alert: t('.error', error: error.message)
|
||||||
|
end
|
||||||
|
|
||||||
|
def vote
|
||||||
|
@poll = Poll.find(params[:id])
|
||||||
|
|
||||||
|
if @poll.one_vote_per_ordergroup
|
||||||
|
ordergroup = current_user.ordergroup
|
||||||
|
return redirect_to polls_path, alert: t('.no_ordergroup') unless ordergroup
|
||||||
|
attributes = { ordergroup: ordergroup }
|
||||||
|
else
|
||||||
|
attributes = { user: current_user }
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to @poll, alert: t('.no_right') unless @poll.user_can_vote?(current_user)
|
||||||
|
|
||||||
|
@poll_vote = @poll.poll_votes.where(attributes).first_or_initialize
|
||||||
|
|
||||||
|
if request.post?
|
||||||
|
@poll_vote.update!(note: params[:note], user: current_user)
|
||||||
|
|
||||||
|
if @poll.single_select?
|
||||||
|
choices = { params[:choice] => '1' }
|
||||||
|
else
|
||||||
|
choices = params[:choices] || {}
|
||||||
|
end
|
||||||
|
|
||||||
|
@poll_vote.poll_choices = choices.map do |choice, value|
|
||||||
|
poll_choice = @poll_vote.poll_choices.where(choice: choice).first_or_initialize
|
||||||
|
poll_choice.update!(value: value)
|
||||||
|
poll_choice
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to @poll
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def user_has_no_right
|
||||||
|
@poll.created_by != current_user && !current_user.role_admin?
|
||||||
|
end
|
||||||
|
|
||||||
|
def poll_params
|
||||||
|
params
|
||||||
|
.require(:poll)
|
||||||
|
.permit(:name, :starts_date_value, :starts_time_value, :ends_date_value,
|
||||||
|
:ends_time_value, :description, :one_vote_per_ordergroup, :voting_method,
|
||||||
|
:multi_select_count, :min_points, :max_points, choices: [],
|
||||||
|
required_ordergroup_custom_fields: [], required_user_custom_fields: [])
|
||||||
|
end
|
||||||
|
end
|
50
plugins/polls/app/models/poll.rb
Normal file
50
plugins/polls/app/models/poll.rb
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
class Poll < ActiveRecord::Base
|
||||||
|
|
||||||
|
# @!attribute required_ordergroup_custom_fields
|
||||||
|
# A list of custom_fileds, which are required to poll.
|
||||||
|
# If the required field on the ordergroup of the user
|
||||||
|
# is empty the user will not be able to place a vote.
|
||||||
|
# @return [Array<String>] Required field names.
|
||||||
|
# @!attribute required_user_custom_fields
|
||||||
|
# A list of custom_fileds, which are required to poll.
|
||||||
|
# If the required field on the user is empty the user
|
||||||
|
# will not be able to place a vote.
|
||||||
|
# @return [Array<String>] Required field names.
|
||||||
|
|
||||||
|
belongs_to :created_by, class_name: 'User', foreign_key: 'created_by_user_id'
|
||||||
|
has_many :poll_votes, dependent: :destroy
|
||||||
|
|
||||||
|
validates_presence_of :name, :choices
|
||||||
|
serialize :choices, Array
|
||||||
|
serialize :required_ordergroup_custom_fields, Array
|
||||||
|
serialize :required_user_custom_fields, Array
|
||||||
|
enum voting_method: { event: 0, single_select: 1, multi_select: 2, points: 3, resistance_points: 4 }
|
||||||
|
|
||||||
|
include DateTimeAttributeValidate
|
||||||
|
date_time_attribute :starts, :ends
|
||||||
|
|
||||||
|
def available_points
|
||||||
|
return 0...0 if min_points.nil? || max_points.nil?
|
||||||
|
min_points..max_points
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_can_edit?(user)
|
||||||
|
created_by == user || user.role_admin?
|
||||||
|
end
|
||||||
|
|
||||||
|
def user_can_vote?(user)
|
||||||
|
Ordergroup.custom_fields.each do |field|
|
||||||
|
name = field[:name]
|
||||||
|
next unless required_ordergroup_custom_fields.include? name
|
||||||
|
return false if user.ordergroup.nil? || user.ordergroup.settings.custom_fields[name].blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
User.custom_fields.each do |field|
|
||||||
|
name = field[:name]
|
||||||
|
next unless required_user_custom_fields.include? name
|
||||||
|
return false if user.settings.custom_fields[name].blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
3
plugins/polls/app/models/poll_choice.rb
Normal file
3
plugins/polls/app/models/poll_choice.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
class PollChoice < ActiveRecord::Base
|
||||||
|
belongs_to :poll_vote, touch: true
|
||||||
|
end
|
6
plugins/polls/app/models/poll_vote.rb
Normal file
6
plugins/polls/app/models/poll_vote.rb
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
class PollVote < ActiveRecord::Base
|
||||||
|
belongs_to :poll
|
||||||
|
belongs_to :ordergroup
|
||||||
|
belongs_to :user
|
||||||
|
has_many :poll_choices, dependent: :destroy
|
||||||
|
end
|
|
@ -0,0 +1,2 @@
|
||||||
|
/ insert_before ':root:first-child'
|
||||||
|
= config_input form, :use_polls, as: :boolean
|
4
plugins/polls/app/views/polls/_choice.haml
Normal file
4
plugins/polls/app/views/polls/_choice.haml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
%div
|
||||||
|
= text_field_tag 'poll[choices][]', value
|
||||||
|
= link_to t('.remove'), "#", title: t('.remove_choice'), 'data-remove-choice' => true,
|
||||||
|
class: 'btn btn-small'
|
76
plugins/polls/app/views/polls/_form.html.haml
Normal file
76
plugins/polls/app/views/polls/_form.html.haml
Normal file
|
@ -0,0 +1,76 @@
|
||||||
|
- content_for :javascript do
|
||||||
|
:javascript
|
||||||
|
var choice = $($.parseHTML("#{escape_javascript(render(partial: 'choice', locals: {value: ''}))}"));
|
||||||
|
|
||||||
|
$(function() {
|
||||||
|
function updateSlideState() {
|
||||||
|
switch($('select[name="poll[voting_method]"]').val()) {
|
||||||
|
case 'event':
|
||||||
|
case 'single_select':
|
||||||
|
$('#multi_select_count').slideUp();
|
||||||
|
$('#min_max_points').slideUp();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'multi_select':
|
||||||
|
$('#multi_select_count').slideDown();
|
||||||
|
$('#min_max_points').slideUp();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'points':
|
||||||
|
case 'resistance_points':
|
||||||
|
$('#multi_select_count').slideUp();
|
||||||
|
$('#min_max_points').slideDown();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$('select[name="poll[voting_method]"]').on('change', updateSlideState);
|
||||||
|
|
||||||
|
$('a[data-remove-choice]').on('click', function() {
|
||||||
|
$(this).parent().remove();
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
$('a[data-add-choice]').on('touchclick', function() {
|
||||||
|
choice.clone().appendTo('#choices');
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
|
||||||
|
updateSlideState();
|
||||||
|
});
|
||||||
|
|
||||||
|
= simple_form_for @poll do |f|
|
||||||
|
= f.input :name
|
||||||
|
.fold-line
|
||||||
|
= f.input :starts, as: :date_picker_time
|
||||||
|
= f.input :ends, as: :date_picker_time
|
||||||
|
= f.input :description, input_html: {rows: 2, class: 'input-xxlarge'}
|
||||||
|
- if @poll.poll_votes.any?
|
||||||
|
= f.input :voting_method, label: false do
|
||||||
|
= f.hint t('.already_voted')
|
||||||
|
- else
|
||||||
|
= f.input :one_vote_per_ordergroup, inline_label: true, label: false
|
||||||
|
- if Ordergroup.custom_fields.any?
|
||||||
|
= f.input :required_ordergroup_custom_fields, as: :check_boxes, label: false,
|
||||||
|
collection: Ordergroup.custom_fields.map{|f| [t('.required_ordergroup_custom_field', label: f[:label]), f[:name]]}.to_h
|
||||||
|
- if User.custom_fields.any?
|
||||||
|
= f.input :required_user_custom_fields, as: :check_boxes, label: false,
|
||||||
|
collection: User.custom_fields.map{|f| [t('.required_user_custom_field', label: f[:label]), f[:name]]}.to_h
|
||||||
|
= f.input :voting_method, collection: Poll.voting_methods,include_blank: false,
|
||||||
|
input_html: {class: 'input-xxlarge'}, value_method: ->(k){ k.first },
|
||||||
|
label_method: ->(k){ t("activerecord.attributes.poll.voting_methods.#{k.first}") }
|
||||||
|
#multi_select_count
|
||||||
|
= f.input :multi_select_count, input_html: { min: 0 }
|
||||||
|
#min_max_points
|
||||||
|
.fold-line
|
||||||
|
= f.input :min_points
|
||||||
|
= f.input :max_points
|
||||||
|
= f.input :choices do
|
||||||
|
#choices
|
||||||
|
= render partial: 'choice', collection: @poll.choices, as: :value
|
||||||
|
- if @poll.choices.empty?
|
||||||
|
= render partial: 'choice', locals: { value: '' }
|
||||||
|
= link_to t('.new_choice'), '#', 'data-add-choice' => true, class: 'btn'
|
||||||
|
.form-actions
|
||||||
|
= f.button :submit
|
||||||
|
= link_to t('ui.or_cancel'), :back
|
23
plugins/polls/app/views/polls/_polls.html.haml
Normal file
23
plugins/polls/app/views/polls/_polls.html.haml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
- if Poll.count > 20
|
||||||
|
= items_per_page
|
||||||
|
= pagination_links_remote @polls
|
||||||
|
%table.table.table-striped
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th= heading_helper Poll, :name
|
||||||
|
%th= heading_helper Poll, :starts
|
||||||
|
%th= heading_helper Poll, :ends
|
||||||
|
%th= t 'ui.actions'
|
||||||
|
%tbody
|
||||||
|
- for poll in @polls
|
||||||
|
%tr{:class => cycle('even','odd', :name => 'polls')}
|
||||||
|
%td= link_to poll.name, poll
|
||||||
|
%td= format_date poll.starts
|
||||||
|
%td= format_date poll.ends
|
||||||
|
%td
|
||||||
|
- if poll.user_can_vote?(current_user)
|
||||||
|
= link_to t('.vote'), vote_poll_path(poll), class: 'btn btn-mini btn-success'
|
||||||
|
- if poll.user_can_edit?(current_user)
|
||||||
|
= link_to t('ui.edit'), edit_poll_path(poll), class: 'btn btn-mini'
|
||||||
|
= link_to t('ui.delete'), poll, :data => {:confirm => t('ui.confirm_delete', name: poll.name)},
|
||||||
|
:method => :delete, class: 'btn btn-mini btn-danger'
|
3
plugins/polls/app/views/polls/edit.html.haml
Normal file
3
plugins/polls/app/views/polls/edit.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
- title t '.title'
|
||||||
|
|
||||||
|
= render 'form'
|
7
plugins/polls/app/views/polls/index.html.haml
Normal file
7
plugins/polls/app/views/polls/index.html.haml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
- title t('.title')
|
||||||
|
|
||||||
|
- content_for :actionbar do
|
||||||
|
= link_to t('.new_poll'), new_poll_path, class: 'btn btn-primary'
|
||||||
|
|
||||||
|
#polls
|
||||||
|
= render 'polls'
|
1
plugins/polls/app/views/polls/index.js.haml
Normal file
1
plugins/polls/app/views/polls/index.js.haml
Normal file
|
@ -0,0 +1 @@
|
||||||
|
$('#polls').html('#{escape_javascript(render("polls"))}');
|
3
plugins/polls/app/views/polls/new.html.haml
Normal file
3
plugins/polls/app/views/polls/new.html.haml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
- title t '.title'
|
||||||
|
|
||||||
|
= render 'form'
|
69
plugins/polls/app/views/polls/show.html.haml
Normal file
69
plugins/polls/app/views/polls/show.html.haml
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
- title @poll.name
|
||||||
|
|
||||||
|
- content_for :actionbar do
|
||||||
|
- if @poll.user_can_vote?(current_user)
|
||||||
|
= link_to t('.vote'), vote_poll_path(@poll), class: 'btn btn-success'
|
||||||
|
- if @poll.user_can_edit?(current_user)
|
||||||
|
= link_to t('ui.edit'), edit_poll_path(@poll), class: 'btn'
|
||||||
|
= link_to t('ui.delete'), @poll, :data => {:confirm => t('.confirm')}, :method => :delete, class: 'btn btn-danger'
|
||||||
|
|
||||||
|
%p= simple_format @poll.description
|
||||||
|
|
||||||
|
- sums = []
|
||||||
|
%table.table.table-striped
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th= heading_helper PollVote, :name
|
||||||
|
- for choice in @poll.choices
|
||||||
|
- sums << 0
|
||||||
|
%th= choice
|
||||||
|
%th= heading_helper PollVote, :updated_at
|
||||||
|
%tbody
|
||||||
|
- for vote in @poll.poll_votes.includes(:poll_choices)
|
||||||
|
%tr
|
||||||
|
%td
|
||||||
|
- if vote.ordergroup.nil?
|
||||||
|
= show_user vote.user
|
||||||
|
- else
|
||||||
|
= "#{vote.ordergroup.name} (#{show_user vote.user})"
|
||||||
|
- @poll.choices.size.times do |idx|
|
||||||
|
- if choice = vote.poll_choices.find { |choice| choice.choice == idx }
|
||||||
|
- if @poll.event?
|
||||||
|
- if choice.value == 0
|
||||||
|
%td{style:'background-color:#eed3d7'}= "\u2715"
|
||||||
|
- elsif choice.value == 1
|
||||||
|
- sums[idx] += 1
|
||||||
|
%td{style:'background-color:#d6e9c6'}= "\u2714"
|
||||||
|
- else
|
||||||
|
- sums[idx] += 0.5
|
||||||
|
%td{style:'background-color:#fcf8e3'}= "?"
|
||||||
|
- elsif @poll.single_select? || @poll.multi_select?
|
||||||
|
- sums[idx] += 1
|
||||||
|
%td= "\u2717"
|
||||||
|
- else
|
||||||
|
- sums[idx] += choice.value
|
||||||
|
%td= choice.value
|
||||||
|
- else
|
||||||
|
%td
|
||||||
|
%td= format_time vote.updated_at
|
||||||
|
%tfoot
|
||||||
|
%tr
|
||||||
|
%td
|
||||||
|
- for sum in sums
|
||||||
|
%td
|
||||||
|
- best_sum = @poll.resistance_points? ? sums.min : sums.max
|
||||||
|
- if sum == best_sum
|
||||||
|
%strong= sum
|
||||||
|
- else
|
||||||
|
= sum
|
||||||
|
%td
|
||||||
|
|
||||||
|
- for vote in @poll.poll_votes
|
||||||
|
- unless vote.note.empty?
|
||||||
|
.comment
|
||||||
|
%strong
|
||||||
|
- if vote.ordergroup.nil?
|
||||||
|
= show_user vote.user
|
||||||
|
- else
|
||||||
|
= "#{vote.ordergroup.name} (#{show_user vote.user})"
|
||||||
|
= simple_format(vote.note)
|
49
plugins/polls/app/views/polls/vote.html.haml
Normal file
49
plugins/polls/app/views/polls/vote.html.haml
Normal file
|
@ -0,0 +1,49 @@
|
||||||
|
- title @poll.name
|
||||||
|
|
||||||
|
%p= simple_format @poll.description
|
||||||
|
|
||||||
|
= form_tag(vote_poll_path(@poll)) do
|
||||||
|
- if @poll.event?
|
||||||
|
%table.table.table-striped
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th
|
||||||
|
%th= "\u2714"
|
||||||
|
%th= "?"
|
||||||
|
%th= "\u2715"
|
||||||
|
%tbody
|
||||||
|
- @poll.choices.each_with_index do |choice, idx|
|
||||||
|
%tr
|
||||||
|
%td= @poll.choices[idx]
|
||||||
|
%td= radio_button_tag "choices[#{idx}]", '1', @poll_vote.poll_choices.where(choice: idx, value: '1').any?
|
||||||
|
%td= radio_button_tag "choices[#{idx}]", '2', @poll_vote.poll_choices.where(choice: idx, value: '2').any?
|
||||||
|
%td= radio_button_tag "choices[#{idx}]", '0', @poll_vote.poll_choices.where(choice: idx, value: '0').any?
|
||||||
|
- elsif @poll.single_select?
|
||||||
|
- @poll.choices.each_with_index do |choice, idx|
|
||||||
|
= radio_button_tag 'choice', idx, @poll_vote.poll_choices.where(choice: idx, value: 1).any?, style: 'float:left'
|
||||||
|
= label_tag "choices[#{idx}]", choice
|
||||||
|
- elsif @poll.multi_select?
|
||||||
|
- @poll.choices.each_with_index do |choice, idx|
|
||||||
|
= check_box_tag "choices[#{idx}]", '1', @poll_vote.poll_choices.where(choice: idx, value: 1).any?, style: 'float:left'
|
||||||
|
= label_tag "choices[#{idx}]", choice
|
||||||
|
- elsif @poll.points? || @poll.resistance_points?
|
||||||
|
%table.table.table-striped
|
||||||
|
%thead
|
||||||
|
%tr
|
||||||
|
%th
|
||||||
|
- @poll.available_points.each do |point|
|
||||||
|
%th= point
|
||||||
|
%tbody
|
||||||
|
- @poll.choices.each_with_index do |choice, idx|
|
||||||
|
%tr
|
||||||
|
%td= @poll.choices[idx]
|
||||||
|
- @poll.available_points.each do |point|
|
||||||
|
%th= radio_button_tag "choices[#{idx}]", point, @poll_vote.poll_choices.where(choice: idx, value: point).any?
|
||||||
|
|
||||||
|
%p
|
||||||
|
%b= heading_helper(PollVote, :note) + ':'
|
||||||
|
%p
|
||||||
|
= text_area_tag 'note', @poll_vote.note
|
||||||
|
%p
|
||||||
|
= submit_tag t('.submit'), class: 'btn btn-success'
|
||||||
|
= link_to t('ui.back'), @poll, class: 'btn'
|
67
plugins/polls/config/locales/de.yml
Normal file
67
plugins/polls/config/locales/de.yml
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
de:
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
poll:
|
||||||
|
choices: Wahlmöglichkeiten
|
||||||
|
created_at: Erstellt am
|
||||||
|
created_by: Erstellt von
|
||||||
|
description: Beschreibung
|
||||||
|
ends: Endet am
|
||||||
|
name: Name
|
||||||
|
max_points: Maximalpunkte
|
||||||
|
min_points: Minimalpunkte
|
||||||
|
multi_select_count: Maximale Anzahl
|
||||||
|
one_vote_per_ordergroup: Nur eine Stimme pro Bestellgruppe
|
||||||
|
starts: Läuft vom
|
||||||
|
voting_method: Umfragetyp
|
||||||
|
voting_methods:
|
||||||
|
event: Termin
|
||||||
|
single_select: Einzelauswahl
|
||||||
|
multi_select: Mehrfachauswahl
|
||||||
|
points: Punkte
|
||||||
|
resistance_points: Widerstandspunkte
|
||||||
|
poll_vote:
|
||||||
|
name: Name
|
||||||
|
note: Notiz
|
||||||
|
updated_at: Zuletzt aktualisierst
|
||||||
|
models:
|
||||||
|
poll: Umfrage
|
||||||
|
config:
|
||||||
|
hints:
|
||||||
|
use_polls: Einfache Umfragen aktivieren
|
||||||
|
keys:
|
||||||
|
use_polls: Umfragen aktivieren
|
||||||
|
navigation:
|
||||||
|
polls: Umfragen
|
||||||
|
polls:
|
||||||
|
choice:
|
||||||
|
remove: Löschen
|
||||||
|
create:
|
||||||
|
error: 'Umfrage konnte nicht erstellt werden: %{error}'
|
||||||
|
notice: Umfrage wurde erstellt
|
||||||
|
edit:
|
||||||
|
title: Umfrage bearbeiten
|
||||||
|
form:
|
||||||
|
already_voted: Die Wahlmöglichkeiten können icht mehr geändert werden, da bereits Stimmen abgegeben wurden.
|
||||||
|
new_choice: Zusätzliche Wahlmöglichkeit
|
||||||
|
required_ordergroup_custom_field: '''%{label}'' von Bestellgruppe darf nicht leer sein'
|
||||||
|
required_user_custom_field: '''%{label}'' von Benutzer_in darf nicht leer sein'
|
||||||
|
destroy:
|
||||||
|
error: 'Umfrage konnte nicht gelöscht werden: %{error}'
|
||||||
|
no_right: Du hast nicht genügend Rechte um die Umfrage zu löschen
|
||||||
|
notice: Umfrage wurde gelöscht
|
||||||
|
index:
|
||||||
|
new_poll: Neue Umfrage
|
||||||
|
title: Umfragen
|
||||||
|
new:
|
||||||
|
title: Umfrage erstellen
|
||||||
|
polls:
|
||||||
|
vote: Abstimmen
|
||||||
|
show:
|
||||||
|
vote: Abstimmen
|
||||||
|
update:
|
||||||
|
error: 'Umfrage konnte nicht aktualisiert werden: %{error}'
|
||||||
|
notice: Umfrage wurde aktualisiert
|
||||||
|
vote:
|
||||||
|
submit: Abstimmen
|
||||||
|
no_right: Du kannst bei dieser Umfrage nicht teilnehmen
|
67
plugins/polls/config/locales/en.yml
Normal file
67
plugins/polls/config/locales/en.yml
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
en:
|
||||||
|
activerecord:
|
||||||
|
attributes:
|
||||||
|
poll:
|
||||||
|
choices: Choices
|
||||||
|
created_at: Created at
|
||||||
|
created_by: Created by
|
||||||
|
description: Description
|
||||||
|
ends: Ends at
|
||||||
|
name: Name
|
||||||
|
max_points: Maximal points
|
||||||
|
min_points: Minimal points
|
||||||
|
multi_select_count: Maximal number
|
||||||
|
one_vote_per_ordergroup: Only one vote for every ordergroup
|
||||||
|
starts: Starts at
|
||||||
|
voting_method: Voting method
|
||||||
|
voting_methods:
|
||||||
|
event: Event
|
||||||
|
single_select: Single select
|
||||||
|
multi_select: Muliple selections
|
||||||
|
points: Points
|
||||||
|
resistance_points: Resistance points
|
||||||
|
poll_vote:
|
||||||
|
name: Name
|
||||||
|
note: Note
|
||||||
|
updated_at: Last updated
|
||||||
|
models:
|
||||||
|
poll: Poll
|
||||||
|
config:
|
||||||
|
hints:
|
||||||
|
use_polls: Enable simple polls.
|
||||||
|
keys:
|
||||||
|
use_polls: Enable polls
|
||||||
|
navigation:
|
||||||
|
polls: Polls
|
||||||
|
polls:
|
||||||
|
choice:
|
||||||
|
remove: Delete
|
||||||
|
create:
|
||||||
|
error: 'Poll could not be created: %{error}'
|
||||||
|
notice: Poll has been created
|
||||||
|
edit:
|
||||||
|
title: Edit poll
|
||||||
|
form:
|
||||||
|
already_voted: The choices can not be changed, since there have been votes already.
|
||||||
|
new_choice: Additional choice
|
||||||
|
required_ordergroup_custom_field: '''%{label}'' of ordergroup must not be empty'
|
||||||
|
required_user_custom_field: '''%{label}'' of use must not be empty'
|
||||||
|
destroy:
|
||||||
|
error: 'Poll could not be deleted: %{error}'
|
||||||
|
no_right: You do not have enough rights to delte this poll
|
||||||
|
notice: Poll has been deleted
|
||||||
|
index:
|
||||||
|
new_poll: New poll
|
||||||
|
title: Polls
|
||||||
|
new:
|
||||||
|
title: New poll
|
||||||
|
polls:
|
||||||
|
vote: Vote
|
||||||
|
show:
|
||||||
|
vote: Vote
|
||||||
|
update:
|
||||||
|
error: 'Poll could not be updated: %{error}'
|
||||||
|
notice: Poll has been updated
|
||||||
|
vote:
|
||||||
|
submit: Vote
|
||||||
|
no_right: You can not participate in this poll
|
14
plugins/polls/config/routes.rb
Normal file
14
plugins/polls/config/routes.rb
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
Rails.application.routes.draw do
|
||||||
|
|
||||||
|
scope '/:foodcoop' do
|
||||||
|
|
||||||
|
resources :polls do
|
||||||
|
member do
|
||||||
|
get :vote
|
||||||
|
post :vote
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
37
plugins/polls/db/migrate/20181110000000_create_polls.rb
Normal file
37
plugins/polls/db/migrate/20181110000000_create_polls.rb
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
class CreatePolls < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :polls do |t|
|
||||||
|
t.integer :created_by_user_id, null: false
|
||||||
|
t.string :name, null: false
|
||||||
|
t.text :description
|
||||||
|
t.datetime :starts
|
||||||
|
t.datetime :ends
|
||||||
|
t.boolean :one_vote_per_ordergroup, default: false, null: false
|
||||||
|
t.text :required_ordergroup_custom_fields
|
||||||
|
t.text :required_user_custom_fields
|
||||||
|
t.integer :voting_method, null: false
|
||||||
|
t.string :choices, array: true, null: false
|
||||||
|
t.integer :final_choice, index: true
|
||||||
|
t.integer :multi_select_count, default: 0, null: false
|
||||||
|
t.integer :min_points
|
||||||
|
t.integer :max_points
|
||||||
|
t.timestamps
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :poll_votes do |t|
|
||||||
|
t.references :poll, null: false
|
||||||
|
t.references :user, null: false
|
||||||
|
t.references :ordergroup
|
||||||
|
t.text :note
|
||||||
|
t.timestamps
|
||||||
|
t.index [:poll_id, :user_id, :ordergroup_id], unique: true
|
||||||
|
end
|
||||||
|
|
||||||
|
create_table :poll_choices do |t|
|
||||||
|
t.references :poll_vote, null: false
|
||||||
|
t.integer :choice, null: false
|
||||||
|
t.integer :value, null: false
|
||||||
|
t.index [:poll_vote_id, :choice], unique: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
21
plugins/polls/foodsoft_polls.gemspec
Normal file
21
plugins/polls/foodsoft_polls.gemspec
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
$:.push File.expand_path("../lib", __FILE__)
|
||||||
|
|
||||||
|
# Maintain your gem's version:
|
||||||
|
require "foodsoft_polls/version"
|
||||||
|
|
||||||
|
# Describe your gem and declare its dependencies:
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.name = "foodsoft_polls"
|
||||||
|
s.version = FoodsoftPolls::VERSION
|
||||||
|
s.authors = ["paroga"]
|
||||||
|
s.email = ["paroga@paroga.com"]
|
||||||
|
s.homepage = "https://github.com/foodcoops/foodsoft"
|
||||||
|
s.summary = "Polls plugin for foodsoft."
|
||||||
|
s.description = "Adds possibility to do polls with foodsoft."
|
||||||
|
|
||||||
|
s.files = Dir["{app,config,db,lib}/**/*"] + ["Rakefile", "README.md"]
|
||||||
|
s.test_files = Dir["test/**/*"]
|
||||||
|
|
||||||
|
s.add_dependency "rails"
|
||||||
|
s.add_dependency "deface", "~> 1.0"
|
||||||
|
end
|
9
plugins/polls/lib/foodsoft_polls.rb
Normal file
9
plugins/polls/lib/foodsoft_polls.rb
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
require 'foodsoft_polls/engine'
|
||||||
|
|
||||||
|
module FoodsoftPolls
|
||||||
|
# Return whether the polls are used or not.
|
||||||
|
# Enabled by default in {FoodsoftConfig} since it used to be part of the foodsoft core.
|
||||||
|
def self.enabled?
|
||||||
|
FoodsoftConfig[:use_polls]
|
||||||
|
end
|
||||||
|
end
|
15
plugins/polls/lib/foodsoft_polls/engine.rb
Normal file
15
plugins/polls/lib/foodsoft_polls/engine.rb
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
module FoodsoftPolls
|
||||||
|
class Engine < ::Rails::Engine
|
||||||
|
def navigation(primary, context)
|
||||||
|
return unless FoodsoftPolls.enabled?
|
||||||
|
return if primary[:foodcoop].nil?
|
||||||
|
sub_nav = primary[:foodcoop].sub_navigation
|
||||||
|
sub_nav.items <<
|
||||||
|
SimpleNavigation::Item.new(primary, :polls, I18n.t('navigation.polls'), context.polls_path)
|
||||||
|
# move to right before tasks item
|
||||||
|
if i = sub_nav.items.index(sub_nav[:tasks])
|
||||||
|
sub_nav.items.insert(i, sub_nav.items.delete_at(-1))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
3
plugins/polls/lib/foodsoft_polls/version.rb
Normal file
3
plugins/polls/lib/foodsoft_polls/version.rb
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module FoodsoftPolls
|
||||||
|
VERSION = "0.0.1"
|
||||||
|
end
|
Loading…
Reference in a new issue