Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow foodcoops to configure their settings #298

Merged
merged 15 commits into from
Sep 2, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions app/assets/stylesheets/bootstrap_and_overrides.css.less
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,23 @@ table {
text-align: center;
}

// Navigation with embedded tabs
.nav-tabs-with-heading {
> li {
line-height: 40px;
margin-top: 16.5px;
a { padding-bottom: 9px; }
}
> li.heading {
> h1, > h2, > h3 {
font-size: 31.5px;
line-height: 40px;
margin: 0;
}
margin: 10px 1em 5px 0;
}
}

// Tasks ..
.accepted {
color: #468847;
Expand Down Expand Up @@ -324,6 +341,15 @@ table.table {
}
}

// inline (boolean) position after/before heading
label {
h1, h2, h3, h4 {
input[type=checkbox] {
margin: auto 0.3em auto 0.3em;
}
}
}

// it's a bit distracting
.icon-asterisk {
font-size: 80%;
Expand Down
39 changes: 39 additions & 0 deletions app/controllers/admin/configs_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
class Admin::ConfigsController < Admin::BaseController

before_action :get_tabs, only: [:show, :list]

def show
@current_tab = @tabs.include?(params[:tab]) ? params[:tab] : @tabs.first
@cfg = FoodsoftConfig
end

def list
@current_tab = 'list'
@cfg = FoodsoftConfig
@dfl = FoodsoftConfig.config
@keys = FoodsoftConfig.keys.select {|k| FoodsoftConfig.allowed_key?(k)}.sort
end

def update
ActiveRecord::Base.transaction do
# TODO support nested configuration keys
params[:config].each do |key, val|
FoodsoftConfig[key] = val
end
end
flash[:notice] = I18n.t('admin.configs.update.notice')
redirect_to action: 'show'
end

protected

# Set configuration tab names as `@tabs`
def get_tabs
@tabs = %w(foodcoop payment tasks messages layout language others)
# allow engines to modify this list
engines = Rails::Engine.subclasses.map(&:instance).select { |e| e.respond_to?(:configuration) }
engines.each { |e| e.configuration(@tabs, self) }
@tabs.uniq!
end

end
26 changes: 25 additions & 1 deletion app/controllers/application_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :select_foodcoop, :authenticate, :store_controller, :items_per_page
after_filter :remove_controller
around_filter :set_time_zone, :set_currency


# Returns the controller handling the current request.
Expand Down Expand Up @@ -167,5 +168,28 @@ def items_per_page
def default_url_options(options = {})
{foodcoop: FoodsoftConfig.scope}
end


# Set timezone according to foodcoop preference.
# @see http://stackoverflow.com/questions/4362663/timezone-with-rails-3
# @see http://archives.ryandaigle.com/articles/2008/1/25/what-s-new-in-edge-rails-easier-timezones
def set_time_zone
old_time_zone = Time.zone
Time.zone = FoodsoftConfig[:time_zone] if FoodsoftConfig[:time_zone]
yield
ensure
Time.zone = old_time_zone
end

# Set currency according to foodcoop preference.
# @see #set_time_zone
def set_currency
old_currency = ::I18n.t('number.currency.format.unit')
new_currency = FoodsoftConfig[:currency_unit] || ''
new_currency += "\u202f" if FoodsoftConfig[:currency_space]
::I18n.backend.store_translations(::I18n.locale, number: {currency: {format: {unit: new_currency}}})
yield
ensure
::I18n.backend.store_translations(::I18n.locale, number: {currency: {format: {unit: old_currency}}})
end

end
131 changes: 131 additions & 0 deletions app/helpers/admin/configs_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
module Admin::ConfigsHelper
# Returns form input for configuration key.
# For configuration keys that contain a {Hash}, {ActiveView::Helpers::FormBuilder#fields_for fields_for} can be used.
# When the key is not {FoodsoftConfig#allowed_key? allowed}, +nil+ is returned.
# @param form [ActionView::Helpers::FormBuilder] Form object.
# @param key [Symbol, String] Configuration key.
# @param options [Hash] Options passed to the form builder.
# @option options [Boolean] :required Wether field is shown as required (default not).
# @return [String] Form input for configuration key.
# @todo find way to pass current value to time_zone input without using default
def config_input(form, key, options = {}, &block)
return unless @cfg.allowed_key? key
options[:label] = config_input_label(form, key)
options[:required] ||= false
options[:input_html] ||= {}
config_input_field_options form, key, options[:input_html]
config_input_tooltip_options form, key, options[:input_html]
if options[:as] == :boolean
options[:input_html][:checked] = 'checked' if v=options[:input_html].delete(:value) and v!='false'
options[:checked_value] = 'true' if options[:checked_value].nil?
options[:unchecked_value] = 'false' if options[:unchecked_value].nil?
elsif options[:collection] or options[:as] == :select
options[:selected] = options[:input_html].delete(:value)
return form.input key, options, &block
elsif options[:as] == :time_zone
options[:default] = options[:input_html].delete(:value)
return form.input key, options, &block
end
form.input key, options, &block
end

# @return [String] Label name in form for configuration key.
# @param form [ActionView::Helpers::FormBuilder] Form object.
# @param key [Symbol, String] Configuration key.
# @see #config_input
def config_input_label(form, key)
cfg_path = form.lookup_model_names[1..-1] + [key]
I18n.t("config.keys.#{cfg_path.map(&:to_s).join('.')}")
end

# @return [String] Form input field for configuration key.
# @see config_input
# @option options [String] :checked_value Value for boolean when checked (default +true+)
# @option options [String] :unchecked_value Value for boolean when not checked (default +false+)
# @todo find out how to pass +checked_value+ and +unchecked_value+ to +input_field+
def config_input_field(form, key, options = {})
return unless @cfg.allowed_key? :key
options[:required] ||= false
config_input_field_options form, key, options
config_input_tooltip_options form, key, options
if options[:as] == :boolean
checked_value = options.delete(:checked_value) || 'true'
unchecked_value = options.delete(:unchecked_value) || 'false'
options[:checked] = 'checked' if v=options.delete(:value) and v!='false'
form.hidden_field(key, value: unchecked_value, as: :hidden) + form.check_box(key, options, checked_value, false)
else
form.input_field key, options
end
end

# @return [String] Form heading with checkbox with block passed in expandable +fieldset+.
# @param form [ActionView::Helpers::FormBuilder] Form object.
# @param key [Symbol, String] Configuration key of a boolean (e.g. +use_messages+).
# @option options [String] :label Label to show
def config_use_heading(form, key, options = {})
head = content_tag :label do
lbl = options[:label] || config_input_label(form, key)
field = config_input_field(form, key, as: :boolean, boolean_style: :inline,
data: {toggle: 'collapse', target: "##{key}-fields"})
content_tag :h4 do
# put in span to keep space for tooltip at right
content_tag :span, (lbl + field).html_safe, config_input_tooltip_options(form, key, {})
end
end
fields = content_tag(:fieldset, id: "#{key}-fields", class: "collapse#{' in' if @cfg[key]}") do
yield
end
head + fields
end

# Returns configuration value suitable for rendering in HTML.
# Makes keys different from +app_config.yml+ configuration bold,
# protects sensitive values like keys and passwords, and makes
# links from URLs.
# @param key [String] Configuration key
# @param value [String] Configuration value
# @return [String] Configuration value suitable for rendering in HTML.
def show_config_value(key, value)
if key =~ /passw|secr|key/
'(protected)'
elsif value.is_a? Hash
content_tag :ul do
value.map do |k,v|
content_tag :li, content_tag(:tt, "#{k}: ") + show_config_value(k, v).to_s
end.join.html_safe
end
elsif value.is_a? Enumerable
content_tag :ul, value.map {|v| content_tag :li, h(v)}.join.html_safe
elsif key =~ /url|website|www|homepage/
link_to(value, value).html_safe
else
value
end
end

private

def config_input_tooltip_options(form, key, options)
# tooltip with help info to the right
cfg_path = form.lookup_model_names[1..-1] + [key]
tooltip = I18n.t("config.hints.#{cfg_path.map(&:to_s).join('.')}", default: '')
unless tooltip.blank?
options[:data] ||= {}
options[:data][:toggle] ||= 'tooltip'
options[:data][:placement] ||= 'right'
options[:title] ||= tooltip
end
options
end

def config_input_field_options(form, key, options)
cfg_path = form.lookup_model_names[1..-1] + [key]
# set current value
unless options.has_key?(:value)
value = @cfg
cfg_path.each {|n| value = value[n.to_sym] if value.respond_to? :[] }
options[:value] = value
end
options
end
end
10 changes: 10 additions & 0 deletions app/views/admin/configs/_tab_foodcoop.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
= config_input form, :name, required: true, input_html: {class: 'input-xlarge'}
= form.fields_for :contact do |c|
= config_input c, :street, input_html: {class: 'input-xlarge'}
.fold-line
= config_input c, :zip_code, input_html: {class: 'input-mini'}
= config_input c, :city, input_html: {class: 'input-medium'}
= config_input c, :country, as: :string, input_html: {class: 'input-xlarge'}
= config_input c, :email, required: true, input_html: {class: 'input-xlarge'}
= config_input c, :phone, input_html: {class: 'input-medium'}
= config_input form, :homepage, required: true, as: :url, input_html: {class: 'input-xlarge'}
9 changes: 9 additions & 0 deletions app/views/admin/configs/_tab_language.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
= config_input form, :default_locale,
collection: I18n.available_locales.map {|l| [t("simple_form.options.settings.profile.language.#{l}"), l]}
= config_input form, :ignore_browser_locale, as: :boolean
= config_input form, :time_zone, as: :time_zone, include_blank: true, input_html: {class: 'input-xlarge'}
= config_input form, :currency_unit do
= config_input_field form, :currency_unit, class: 'input-mini'
%label.inline
= config_input_label form, :currency_space
= config_input_field form, :currency_space, as: :boolean
15 changes: 15 additions & 0 deletions app/views/admin/configs/_tab_layout.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
= config_input form, :page_footer, as: :text, input_html: {class: 'input-xxlarge', rows: 2, placeholder: "#{link_to(FoodsoftConfig[:name], FoodsoftConfig[:homepage])}"}
= config_input form, :custom_css, as: :text, input_html: {class: 'input-xxlarge', rows: 3}

%h4= t '.pdf_title'
.fold-line
= config_input form, :pdf_font_size, as: :integer, input_html: {class: 'input-mini'}
= config_input form, :pdf_page_size, input_html: {class: 'input-medium'}
= config_input form, :pdf_add_page_breaks do
= form.simple_fields_for :pdf_add_page_breaks do |fields|
%label
= config_input_field fields, :order_by_groups, as: :boolean
= t 'config.hints.pdf_add_page_breaks.order_by_groups'
%label
= config_input_field fields, :order_by_articles, as: :boolean
= t 'config.hints.pdf_add_page_breaks.order_by_articles'
7 changes: 7 additions & 0 deletions app/views/admin/configs/_tab_messages.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
%fieldset
%label
%h4= t '.emails_title'
= config_input form, :email_from, as: :string, input_html: {class: 'input-xlarge', placeholder: "#{@cfg[:name]} <#{@cfg[:contact][:email]}>"}
= config_input form, :email_replyto, as: :string, input_html: {class: 'input-xlarge'}
-# sender is better configured by server administrator, since it affects SPF records
-#= config_input form, :email_sender, as: :string, input_html: {class: 'input-xlarge'}
5 changes: 5 additions & 0 deletions app/views/admin/configs/_tab_others.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
- if defined? FoodsoftWiki # avoid requiring deface here (the single exception)
= config_input form, :use_wiki, as: :boolean
= config_input form, :use_nick, as: :boolean
= config_input form, :tolerance_is_costly, as: :boolean
= config_input form, :help_url, as: :url, input_html: {class: 'input-xlarge'}
12 changes: 12 additions & 0 deletions app/views/admin/configs/_tab_payment.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
= config_input form, :price_markup do
.input-append
= config_input_field form, :price_markup, as: :decimal, class: 'input-mini'
%span.add-on %
= config_input form, :tax_default do
.input-append
= config_input_field form, :tax_default, as: :decimal, class: 'input-mini'
%span.add-on %
= config_input form, :minimum_balance do
.input-prepend
%span.add-on= t 'number.currency.format.unit'
= config_input_field form, :minimum_balance, as: :decimal, class: 'input-small'
3 changes: 3 additions & 0 deletions app/views/admin/configs/_tab_tasks.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-#= config_use_heading form, :use_tasks do
= config_use_heading form, :use_apple_points do
= config_input form, :stop_ordering_under, as: :numeric, input_html: {class: 'input-small'}
11 changes: 11 additions & 0 deletions app/views/admin/configs/_tabs.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
%ul.nav.nav-tabs.nav-tabs-with-heading
%li.heading
%h1= t '.title'

- for tab in @tabs
- url = action_name == 'show' ? nil : admin_config_path(tab: tab)
%li{class: ('active' if @current_tab==tab)}= link_to t("config.tabs.#{tab}"), "#{url}#tab-#{tab}", data: ({toggle: 'tab'} unless url)

-# make this a button to give some indicator that navigation away might lose changes
%li.pull-right{class: ('active' if @current_tab=='list')}
= link_to t('config.tabs.list'), list_admin_config_path, class: ('btn' unless @current_tab=='list')
16 changes: 16 additions & 0 deletions app/views/admin/configs/list.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
- title t('.title'), false

= render 'tabs'

%table.table
%thead
%tr
%th= t '.key'
%th= t '.value'
%tbody
- @keys.each do |key|
%tr
%td
%tt= key
%td{style: if @cfg[key] != @dfl[key] then 'font-weight: bold' end}
= show_config_value key, @cfg[key]
13 changes: 13 additions & 0 deletions app/views/admin/configs/show.html.haml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
- title t('admin.configs.tabs.title'), false

= simple_form_for :config, method: :patch do |f|

= render 'tabs', url: nil

.tab-content
- for tab in @tabs
.tab-pane{class: ('active' if @current_tab==tab), id: "tab-#{tab}"}= render "tab_#{tab}", form: f

.form-actions
= f.submit t('.submit'), class: 'btn btn-primary'

6 changes: 3 additions & 3 deletions app/views/layouts/application.html.haml
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
%li= link_to t('.profile'), my_profile_path
%li= link_to t('.ordergroup'), my_ordergroup_path
%li= link_to t('.logout'), logout_path
%li{class: ('disabled' if FoodsoftConfig.config[:homepage].blank?)}
= link_to FoodsoftConfig.config[:name], FoodsoftConfig.config[:homepage]
%li= link_to t('.help'), FoodsoftConfig.config[:help_url]
%li{class: ('disabled' if FoodsoftConfig[:homepage].blank?)}
= link_to FoodsoftConfig[:name], FoodsoftConfig[:homepage]
%li= link_to t('.help'), FoodsoftConfig[:help_url]
%li= link_to t('.feedback.title'), new_feedback_path, title: t('.feedback.desc')
.clearfix

Expand Down
5 changes: 5 additions & 0 deletions config/app_config.yml.SAMPLE
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ default: &defaults
# In case you really want foodsoft in a certain language by default, set this to true.
# When members are logged in, the language from their profile settings is still used.
#ignore_browser_locale: false
# Default timezone, e.g. UTC, Amsterdam, Berlin, etc.
#time_zone: Berlin
# Currency symbol, and whether to add a whitespace after the unit.
#currency_unit: €
#currency_space: true

# price markup in percent
price_markup: 2.0
Expand Down
3 changes: 3 additions & 0 deletions config/environments/test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
# and recreated between test runs. Don't rely on the data there!
config.cache_classes = true

# We clear the cache for each test, let's do that in memory.
config.cache_store = :memory_store

# Do not eager load code on boot. This avoids loading your whole application
# just for the purpose of running a single test. If you are using a tool that
# preloads Rails for running tests, you may have to set it to true.
Expand Down
Loading