From dd3ac0971ce7e8b6e0123971c544c5561df0c943 Mon Sep 17 00:00:00 2001 From: wvengen Date: Sun, 16 Mar 2014 02:07:54 +0100 Subject: [PATCH 01/15] allow to put foodcoop config in database --- db/schema.rb | 12 ------------ lib/foodsoft_config.rb | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index 5b3cd143..c8e9c41e 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -66,18 +66,6 @@ ActiveRecord::Schema.define(version: 20140521142651) do add_index "assignments", ["user_id", "task_id"], name: "index_assignments_on_user_id_and_task_id", unique: true, using: :btree - create_table "configurable_settings", force: true do |t| - t.integer "configurable_id" - t.string "configurable_type" - t.integer "targetable_id" - t.string "targetable_type" - t.string "name", default: "", null: false - t.string "value_type" - t.text "value" - end - - add_index "configurable_settings", ["name"], name: "index_configurable_settings_on_name", using: :btree - create_table "deliveries", force: true do |t| t.integer "supplier_id" t.date "delivered_on" diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index 69644705..a90004e8 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -26,7 +26,7 @@ class FoodsoftConfig # Provides a nice accessor for config values # FoodsoftConfig[:name] # => 'FC Test' def [](key) - config[key] + RailsSettings::CachedSettings["foodcoop.#{self.scope}.#{key}"] || config[key] end # Loop through each foodcoop and executes the given block after setup config and database From 7b000c39ebc279f3b71e1de73aad34cbaffff791 Mon Sep 17 00:00:00 2001 From: wvengen Date: Sun, 16 Mar 2014 02:08:15 +0100 Subject: [PATCH 02/15] add foodcoop configuration screen --- .../bootstrap_and_overrides.css.less | 26 ++++ app/controllers/admin/configs_controller.rb | 39 ++++++ app/helpers/admin/configs_helper.rb | 117 +++++++++++++++++ .../admin/configs/_tab_foodcoop.html.haml | 10 ++ .../admin/configs/_tab_language.html.haml | 3 + app/views/admin/configs/_tab_layout.html.haml | 7 ++ .../admin/configs/_tab_messages.html.haml | 7 ++ app/views/admin/configs/_tab_others.html.haml | 5 + .../admin/configs/_tab_payment.html.haml | 12 ++ app/views/admin/configs/_tab_tasks.html.haml | 3 + app/views/admin/configs/_tabs.html.haml | 11 ++ app/views/admin/configs/list.html.haml | 16 +++ app/views/admin/configs/show.html.haml | 13 ++ app/views/layouts/application.html.haml | 6 +- config/locales/en.yml | 86 +++++++++++++ config/locales/nl.yml | 86 +++++++++++++ config/navigation.rb | 1 + config/routes.rb | 4 + lib/foodsoft_config.rb | 118 ++++++++++++++++-- .../_tab_messages/add_config.html.haml.deface | 4 + 20 files changed, 562 insertions(+), 12 deletions(-) create mode 100644 app/controllers/admin/configs_controller.rb create mode 100644 app/helpers/admin/configs_helper.rb create mode 100644 app/views/admin/configs/_tab_foodcoop.html.haml create mode 100644 app/views/admin/configs/_tab_language.html.haml create mode 100644 app/views/admin/configs/_tab_layout.html.haml create mode 100644 app/views/admin/configs/_tab_messages.html.haml create mode 100644 app/views/admin/configs/_tab_others.html.haml create mode 100644 app/views/admin/configs/_tab_payment.html.haml create mode 100644 app/views/admin/configs/_tab_tasks.html.haml create mode 100644 app/views/admin/configs/_tabs.html.haml create mode 100644 app/views/admin/configs/list.html.haml create mode 100644 app/views/admin/configs/show.html.haml create mode 100644 lib/foodsoft_messages/app/overrides/admin/configs/_tab_messages/add_config.html.haml.deface diff --git a/app/assets/stylesheets/bootstrap_and_overrides.css.less b/app/assets/stylesheets/bootstrap_and_overrides.css.less index 614b2732..a7e25293 100644 --- a/app/assets/stylesheets/bootstrap_and_overrides.css.less +++ b/app/assets/stylesheets/bootstrap_and_overrides.css.less @@ -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; @@ -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%; diff --git a/app/controllers/admin/configs_controller.rb b/app/controllers/admin/configs_controller.rb new file mode 100644 index 00000000..04d93b4f --- /dev/null +++ b/app/controllers/admin/configs_controller.rb @@ -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 diff --git a/app/helpers/admin/configs_helper.rb b/app/helpers/admin/configs_helper.rb new file mode 100644 index 00000000..8124c2ca --- /dev/null +++ b/app/helpers/admin/configs_helper.rb @@ -0,0 +1,117 @@ +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. + # @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. + def config_input(form, key, options = {}, &block) + 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 options[:input_html].delete(:value) + options[:checked_value] = true + options[:unchecked_value] = false + elsif options[:collection] or options[:as] == :select + options[:selected] = 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 + # @todo find out how to pass +checked_value+ and +unchecked_value+ to +input_field+ + def config_input_field(form, key, options = {}) + config_input_field_options form, key, options + config_input_tooltip_options form, key, options + if options[:as] == :boolean + options[:checked] = 'checked' if options.delete(:value) + form.hidden_field(key, value: false, as: :hidden) + form.check_box(key, options, true, 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 + value = @cfg + cfg_path.each {|n| value = value[n] unless value.nil? } + options[:value] ||= value + options + end +end diff --git a/app/views/admin/configs/_tab_foodcoop.html.haml b/app/views/admin/configs/_tab_foodcoop.html.haml new file mode 100644 index 00000000..5aea80c2 --- /dev/null +++ b/app/views/admin/configs/_tab_foodcoop.html.haml @@ -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'} diff --git a/app/views/admin/configs/_tab_language.html.haml b/app/views/admin/configs/_tab_language.html.haml new file mode 100644 index 00000000..4debea6a --- /dev/null +++ b/app/views/admin/configs/_tab_language.html.haml @@ -0,0 +1,3 @@ += 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 diff --git a/app/views/admin/configs/_tab_layout.html.haml b/app/views/admin/configs/_tab_layout.html.haml new file mode 100644 index 00000000..8d37da66 --- /dev/null +++ b/app/views/admin/configs/_tab_layout.html.haml @@ -0,0 +1,7 @@ += config_input form, :page_footer, as: :text, input_html: {class: 'input-xxlarge', rows: 2, placeholder: "#{link_to(FoodsoftConfig[:name], FoodsoftConfig[:homepage])}"} + +%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, as: :boolean diff --git a/app/views/admin/configs/_tab_messages.html.haml b/app/views/admin/configs/_tab_messages.html.haml new file mode 100644 index 00000000..5ffd4e54 --- /dev/null +++ b/app/views/admin/configs/_tab_messages.html.haml @@ -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'} diff --git a/app/views/admin/configs/_tab_others.html.haml b/app/views/admin/configs/_tab_others.html.haml new file mode 100644 index 00000000..69bb2867 --- /dev/null +++ b/app/views/admin/configs/_tab_others.html.haml @@ -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'} diff --git a/app/views/admin/configs/_tab_payment.html.haml b/app/views/admin/configs/_tab_payment.html.haml new file mode 100644 index 00000000..d3d62cc6 --- /dev/null +++ b/app/views/admin/configs/_tab_payment.html.haml @@ -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' diff --git a/app/views/admin/configs/_tab_tasks.html.haml b/app/views/admin/configs/_tab_tasks.html.haml new file mode 100644 index 00000000..180e789a --- /dev/null +++ b/app/views/admin/configs/_tab_tasks.html.haml @@ -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'} diff --git a/app/views/admin/configs/_tabs.html.haml b/app/views/admin/configs/_tabs.html.haml new file mode 100644 index 00000000..6db4cebf --- /dev/null +++ b/app/views/admin/configs/_tabs.html.haml @@ -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') diff --git a/app/views/admin/configs/list.html.haml b/app/views/admin/configs/list.html.haml new file mode 100644 index 00000000..be6148b4 --- /dev/null +++ b/app/views/admin/configs/list.html.haml @@ -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] diff --git a/app/views/admin/configs/show.html.haml b/app/views/admin/configs/show.html.haml new file mode 100644 index 00000000..671121cc --- /dev/null +++ b/app/views/admin/configs/show.html.haml @@ -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' + diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index bf65a3d9..506cd51b 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -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 diff --git a/config/locales/en.yml b/config/locales/en.yml index 154c50a3..79e956b7 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -225,6 +225,22 @@ en: title: Administration type: type username: username + configs: + list: + title: Configuration list + key: Key + value: Value + show: + title: Configuration + submit: Save + tab_layout: + pdf_title: PDF documents + tabs: + title: Configuration + tab_messages: + emails_title: Sending email + update: + notice: Configuration saved. confirm: Do you really want to delete %{name}? ordergroups: destroy: @@ -419,6 +435,75 @@ en: file_label: Please choose a compatible file submit: Upload file title: '%{supplier} / upload articles' + config: + tabs: + foodcoop: Foodcoop + payment: Finances + language: Language + messages: Messages + tasks: Tasks + layout: Layout + others: Other + list: List + hints: + name: The name of your foodcoop. + contact: + email: General contact email address, shown on website as well as some forms. + street: Address, typically this will be your delivery and pick-up location. + homepage: Website of your foodcoop. + help_url: Documentation website. + applepear_url: Website where the apple and pear system for tasks is explained. + ignore_browser_locale: "Ignore the language of user's computer when the user has not chosen a language yet." + price_markup: Percentage that is added to the gross price for foodcoop members. + tax_default: Default VAT percentage for new articles. + stop_ordering_under: Members can only order when they have at least this many apple points. + minimum_balance: Members can only order when their account balance is above or equal to this amount. + use_apple_points: When the apple point system is enabled, members are required to do some tasks to be able to keep ordering. + use_nick: Show and use nicknames instead of real names. When enabling this, please check that each user has a nickname. + use_messages: Allow members to communicate with each other within Foodsoft. + use_wiki: Enable editable wiki pages. + email_sender: "Emails will appear to be sent from this email address. To avoid emails sent being classified as spam, the webserver may need to be registered in the SPF record of the email address's domain." + email_from: "Emails will appear to be from this email address. Leave empty to use the foodcoop's contact address." + email_replyto: Set this when you want to receive replies from emails sent by Foodsoft on a different address than the above. + mailing_list: Mailing-list email address to use instead of the messaging system for mail to all members. + mailing_list_subscribe: Email address where members can send an email to for subscribing. + page_footer: 'Shown on each page at the bottom. Enter "blank" to disable the footer completely.' + pdf_font_size: Base font size for PDF documents (12 is standard). + pdf_page_size: 'Page size for PDF documents, typically "A4" or "letter".' + pdf_add_page_breaks: Add page breaks when starting a new section. + tolerance_is_costly: "Order as much of the member tolerance as possible (compared to only as much needed to fill the last box). Enabling this also includes the tolerance in the total price of the open member order." + keys: + name: Name + contact: + street: Street + zip_code: Postcode + city: City + country: Country + email: Email + phone: Phone + homepage: Homepage + help_url: Documentation URL + applepear_url: Apple system URL + default_locale: Default language + ignore_browser_locale: Ignore browser language + price_markup: Foodcoop margin + tax_default: Default VAT + tolerance_is_costly: Tolerance is costly + stop_ordering_under: Minimum apple points + use_apple_points: Apple points + minimum_balance: Minimum account balance + use_nick: Use nicknames + use_messages: Enable messages + use_wiki: Enable wiki + email_sender: Sender address + email_from: From address + email_replyto: Reply-to address + mailing_list: Mailing-list + mailing_list_subscribe: Mailing-list subscribe + page_footer: Page footer + pdf_font_size: Font size + pdf_page_size: Page size + pdf_add_page_breaks: Page breaks deliveries: add_stock_change: how_many_units: 'How many units (%{unit}) to deliver? Stock article name: %{name}.' @@ -1043,6 +1128,7 @@ en: no_ordergroup: no ordergroup navigation: admin: + config: Configuration home: Overview ordergroups: Ordergroups title: Administration diff --git a/config/locales/nl.yml b/config/locales/nl.yml index f3f38c0d..56313c7a 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -213,6 +213,22 @@ nl: title: Administratie type: Type username: Gebruikersnaam + configs: + list: + title: Configuratielijst + key: Sleutel + value: Inhoud + show: + title: Configuratie + submit: Opslaan + tab_layout: + pdf_title: PDF documenten + tabs: + title: Configuratie + tab_messages: + emails_title: Emailinstellingen + update: + notice: Configuratie opgeslagen. confirm: Wil je %{name} daadwerkelijk wissen? ordergroups: destroy: @@ -402,6 +418,75 @@ nl: file_label: Graag een compatibel bestand uitkiezen submit: Bestand uploaden title: Artikelen uploaden voor %{supplier} + config: + tabs: + foodcoop: Foodcoop + payment: Financiën + language: Taal + messages: Berichten + tasks: Taken + layout: Layout + others: Overig + list: Lijst + hints: + name: De naam van de foodcoop. + contact: + email: Algemeen contactadres, zowel voor op de website als in formulieren. + street: Adres, meestal is dit het aflever- en ophaaladres. + homepage: Website van de foodcoop. + help_url: Documentatie website. + applepear_url: Website waar het appelpunten systeem wordt uitgelegd. + ignore_browser_locale: Negeer de taal van de computer wanneer iemand nog geen taal gekozen heeft. + price_markup: Percentage dat bovenop het brutobedrag wordt gedaan voor foodcoop leden. + tax_default: Standaard BTW percentage voor nieuwe artikelen. + stop_ordering_under: Leden kunnen slechts bestellen als ze dit aantal appelpunten hebben of meer. + minimum_balance: Leden kunnen slechts bestellen wanneer hun tegoed groter of gelijk is aan dit bedrag. + use_apple_points: Wanneer het appelpunten systeem is geactiveerd, kunnen leden slechts bestellen wanneer ze meewerken aan taken. + use_nick: Toon bijnamen in plaats van volledige naam. Controleer dat iedereen een bijnaam heeft wanneer je dit aanzet. + use_messages: Laat leden met elkaar communiceren door middel van berichten binnen Foodsoft. + use_wiki: "Gebruik wiki pagina's." + email_sender: "Emails worden verzonden vanaf dit emailadres. Om te voorkomen dat emails als spam worden tegengehouden, is het te adviseren het adres van de webserver op te nemen in het SPF record van het email domein." + email_from: "Emails zullen lijken verzonden te zijn vanaf dit email adres. Laat het veld leeg om het contactadres van de foodcoop te gebruiken." + email_replyto: Vul dit in als je antwoord op mails van Foodsoft wilt ontvangen op een ander adres dan het bovenstaande. + mailing_list: Mailing-lijst adres om te gebruiken in plaats van het berichtensysteem voor emails naar alle leden. + mailing_list_subscribe: Emailadres waar leden zich kunnen aanmelden voor de mailing-lijst. + page_footer: 'Wordt op iedere pagina getoond. Vul "blank" in om de voettekst helemaal weg te halen.' + pdf_font_size: Basis tekstgrootte voor PDF bestanden (standaard 12). + pdf_page_size: 'Paginaformaat voor PDF bestanden, meestal "A4" of "letter".' + pdf_add_page_breaks: Secties op een nieuwe pagina beginnen. + tolerance_is_costly: "Bestel zoveel artikelen als mogelijk in de tolerantie (in plaats van net genoeg om de laatste doos te vullen). Dit zorgt er ook voor dat de tolerantie in de prijs van open ledenbestellingen wordt meegenomen." + keys: + name: Naam + contact: + street: Straat + zip_code: Postcode + city: Stad + country: Land + email: Email + phone: Telefoon + homepage: Homepage + help_url: Documentatie URL + applepear_url: Appelsysteem uitleg URL + default_locale: Standaardtaal + ignore_browser_locale: Browsertaal negeren + price_markup: Foodcoop marge + tax_default: Standaard BTW + tolerance_is_costly: Tolerantie is duur + stop_ordering_under: Minimum appelpunten + use_apple_points: Appelpunten + minimum_balance: Minimum tegoed + use_nick: Bijnamen gebruiken + use_messages: Berichten gebruiken + use_wiki: Wiki gebruiken + email_sender: Sender adres + email_from: From adres + email_replyto: Reply-to adres + mailing_list: Mailing-lijst + mailing_list_subscribe: Mailing-lijst aanmelden + page_footer: Voettkest + pdf_font_size: Tekstgrootte + pdf_page_size: Paginaformaat + pdf_add_page_breaks: "Nieuwe pagina's" deliveries: add_stock_change: how_many_units: 'Hoeveel eenheden (%{unit}) leveren? Voorraadartikel: %{name}.' @@ -1023,6 +1108,7 @@ nl: no_ordergroup: geen huishouden navigation: admin: + config: Instellingen home: Overzicht ordergroups: Huishoudens title: Administratie diff --git a/config/navigation.rb b/config/navigation.rb index de4e6b24..dd5d1f17 100644 --- a/config/navigation.rb +++ b/config/navigation.rb @@ -45,6 +45,7 @@ SimpleNavigation::Configuration.run do |navigation| subnav.item :users, I18n.t('navigation.admin.users'), admin_users_path subnav.item :ordergroups, I18n.t('navigation.admin.ordergroups'), admin_ordergroups_path subnav.item :workgroups, I18n.t('navigation.admin.workgroups'), admin_workgroups_path + subnav.item :config, I18n.t('navigation.admin.config'), admin_config_path end engines.each { |e| e.navigation(primary, self) } diff --git a/config/routes.rb b/config/routes.rb index 42921214..aa87ae25 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -181,6 +181,10 @@ Foodsoft::Application.routes.draw do resources :ordergroups do get :memberships, :on => :member end + + resource :config, :only => [:show, :update] do + get :list + end end ############## Feedback diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index a90004e8..33ad2524 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -1,9 +1,37 @@ +# Foodcoop-specific configuration. +# +# This is loaded from +config/app_config.yml+, which contains a root +# key for each environment (plus an optional +defaults+ key). When using +# the multicoops feature (+multicoops+ is set to +true+ for the environment), +# each foodcoop has its own key. +# +# In addition to the configuration file, values can be overridden in the database +# using {RailsSettings::CachedSettings} as +foodcoop..**+. +# class FoodsoftConfig - mattr_accessor :scope, :config + + # @!attribute scope + # Returns the current foodcoop scope for the multicoops feature, otherwise + # the value of the foodcoop configuration key +default_scope+ is used. + # @return [String] The current foodcoop scope. + mattr_accessor :scope + # @!attribute config + # Returns a {ActiveSupport::HashWithIndifferentAccess Hash} with the current + # scope's configuration from the configuration file. Note that this does not + # include values that were changed in the database. + # @return [ActiveSupport::HashWithIndifferentAccess] Current configuration from configuration file. + mattr_accessor :config + + # Configuration file location. + # Taken from environment variable +FOODSOFT_APP_CONFIG+, + # or else +config/app_config.yml+. APP_CONFIG_FILE = ENV['FOODSOFT_APP_CONFIG'] || 'config/app_config.yml' # Rails.logger isn't ready yet - and we don't want to litter rspec invocation with this msg puts "-> Loading app configuration from #{APP_CONFIG_FILE}" unless defined? RSpec - APP_CONFIG = YAML.load(File.read(File.expand_path(APP_CONFIG_FILE, Rails.root))) + # Loaded configuration + APP_CONFIG = ActiveSupport::HashWithIndifferentAccess.new( + YAML.load(File.read(File.expand_path(APP_CONFIG_FILE, Rails.root))) + ) class << self @@ -16,17 +44,71 @@ class FoodsoftConfig set_missing end - # Set config and database connection for specific foodcoop - # Only needed in multi coop mode + # Set config and database connection for specific foodcoop. + # + # Only needed in multi coop mode. + # @param foodcoop [String, Symbol] Foodcoop to select. def select_foodcoop(foodcoop) set_config foodcoop setup_database end - # Provides a nice accessor for config values - # FoodsoftConfig[:name] # => 'FC Test' + # Return configuration value for the currently selected foodcoop. + # + # First tries to read configuration from the database (cached), + # then from the configuration files. + # + # FoodsoftConfig[:name] # => 'FC Test' + # + # @param key [String, Symbol] + # @return [Object] Value of the key. def [](key) - RailsSettings::CachedSettings["foodcoop.#{self.scope}.#{key}"] || config[key] + if allowed_key?(key) + value = RailsSettings::CachedSettings["foodcoop.#{self.scope}.#{key}"] + value = config[key] if value.nil? + value + else + config[key] + end + end + + # Store configuration in the database. + # + # If value is equal to what's defined in the configuration file, remove key from the database. + # @param key [String, Symbol] Key + # @param value [Object] Value + # @return [Boolean] Whether storing succeeded (fails when key is not allowed to be set in database). + def []=(key, value) + return false unless allowed_key?(key) + # try to figure out type ... + value = case value + when 'true' then true + when 'false' then false + when /^[-+0-9]+$/ then value.to_i + when /^[-+0-9.]+([eE][-+0-9]+)?$/ then value.to_f + when '' then nil + else value + end + # then update database + if config[key] == value or (config[key].nil? and value == false) + # delete (ok if it was already deleted) + begin + RailsSettings::CachedSettings.destroy "foodcoop.#{self.scope}.#{key}" + rescue RailsSettings::Settings::SettingNotFound + end + else + # or store + RailsSettings::CachedSettings["foodcoop.#{self.scope}.#{key}"] = value + end + return true + end + + # @return [Array] Configuration keys that are set (either in +app_config.yml+ or database). + def keys + keys = RailsSettings::CachedSettings.get_all("foodcoop.#{self.scope}.").try(:keys) || [] + keys.map! {|k| k.gsub /^foodcoop\.#{self.scope}\./, ''} + keys += config.keys + keys.map(&:to_s).uniq end # Loop through each foodcoop and executes the given block after setup config and database @@ -41,11 +123,18 @@ class FoodsoftConfig end end + # @return [Boolean] Whether this key may be set in the database + def allowed_key?(key) + # fast check for keys without nesting + return !self.config[:protected].keys.include?(key) + # @todo allow to check nested keys as well + end + private def set_config(foodcoop) raise "No config for this environment (#{foodcoop}) available!" if APP_CONFIG[foodcoop].nil? - self.config = APP_CONFIG[foodcoop].symbolize_keys + self.config = APP_CONFIG[foodcoop] self.scope = foodcoop set_missing end @@ -62,7 +151,18 @@ class FoodsoftConfig config.replace({ use_nick: true, use_apple_points: true, - foodsoft_url: 'https://github.com/foodcoops/foodsoft' + # English is the default language, and this makes it show up as default. + default_locale: 'en', + foodsoft_url: 'https://github.com/foodcoops/foodsoft', + # The following keys cannot be set by foodcoops themselves. + protected: { + multi_coop_install: nil, + default_scope: nil, + notification: nil, + shared_lists: nil, + protected: nil, + database: nil + } }.merge(config)) end diff --git a/lib/foodsoft_messages/app/overrides/admin/configs/_tab_messages/add_config.html.haml.deface b/lib/foodsoft_messages/app/overrides/admin/configs/_tab_messages/add_config.html.haml.deface new file mode 100644 index 00000000..a948d4fd --- /dev/null +++ b/lib/foodsoft_messages/app/overrides/admin/configs/_tab_messages/add_config.html.haml.deface @@ -0,0 +1,4 @@ +/ insert_bottom ':root' += config_use_heading form, :use_messages, label: 'Messages' do + = config_input form, :mailing_list, as: :string, input_html: {class: 'input-xlarge'} + = config_input form, :mailing_list_subscribe, as: :string, input_html: {class: 'input-xlarge'} From 90f60595e6d3c7347b8bff3d1f8389b2ade85e90 Mon Sep 17 00:00:00 2001 From: wvengen Date: Sat, 21 Jun 2014 12:52:50 +0200 Subject: [PATCH 03/15] don't fail in foodsoft_config without database, and use proper config defaults so that configuration editing has sensible default values --- lib/foodsoft_config.rb | 11 +++++++++-- lib/foodsoft_messages/lib/foodsoft_messages.rb | 4 ++-- lib/foodsoft_wiki/lib/foodsoft_wiki.rb | 4 ++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index 33ad2524..71d9426c 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -60,10 +60,14 @@ class FoodsoftConfig # # FoodsoftConfig[:name] # => 'FC Test' # + # To avoid errors when the database is not yet setup (when loading + # the initial database schema), cached settings are only being read + # when the settings table exists. + # # @param key [String, Symbol] # @return [Object] Value of the key. def [](key) - if allowed_key?(key) + if RailsSettings::CachedSettings.table_exists? and allowed_key?(key) value = RailsSettings::CachedSettings["foodcoop.#{self.scope}.#{key}"] value = config[key] if value.nil? value @@ -146,10 +150,13 @@ class FoodsoftConfig end # When new options are introduced, put backward-compatible defaults here, so that - # configuration files that haven't been updated, still work as they did. + # configuration files that haven't been updated, still work as they did. This also + # makes sure that the configuration editor picks up the defaults. def set_missing config.replace({ use_nick: true, + use_wiki: true, + use_messages: true, use_apple_points: true, # English is the default language, and this makes it show up as default. default_locale: 'en', diff --git a/lib/foodsoft_messages/lib/foodsoft_messages.rb b/lib/foodsoft_messages/lib/foodsoft_messages.rb index 9cc7c4a6..08619383 100644 --- a/lib/foodsoft_messages/lib/foodsoft_messages.rb +++ b/lib/foodsoft_messages/lib/foodsoft_messages.rb @@ -4,8 +4,8 @@ require "deface" module FoodsoftMessages # Return whether messages are used or not. - # Enabled by default since it used to be part of the foodsoft core. + # Enabled by default in {FoodsoftConfig} since it used to be part of the foodsoft core. def self.enabled? - FoodsoftConfig[:use_messages] != false + FoodsoftConfig[:use_messages] end end diff --git a/lib/foodsoft_wiki/lib/foodsoft_wiki.rb b/lib/foodsoft_wiki/lib/foodsoft_wiki.rb index dc726e81..0d2aa77f 100644 --- a/lib/foodsoft_wiki/lib/foodsoft_wiki.rb +++ b/lib/foodsoft_wiki/lib/foodsoft_wiki.rb @@ -6,8 +6,8 @@ require 'foodsoft_wiki/engine' module FoodsoftWiki # Return whether the wiki is used or not. - # Enabled by default since it used to be part of the foodsoft core. + # Enabled by default in {FoodsoftConfig} since it used to be part of the foodsoft core. def self.enabled? - FoodsoftConfig[:use_wiki] != false + FoodsoftConfig[:use_wiki] end end From 429e111db4bf7205db3c3e480748879d8bd4683f Mon Sep 17 00:00:00 2001 From: wvengen Date: Sat, 21 Jun 2014 18:01:02 +0200 Subject: [PATCH 04/15] hide protected keys from config --- app/helpers/admin/configs_helper.rb | 3 +++ lib/foodsoft_config.rb | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/app/helpers/admin/configs_helper.rb b/app/helpers/admin/configs_helper.rb index 8124c2ca..4f671fb7 100644 --- a/app/helpers/admin/configs_helper.rb +++ b/app/helpers/admin/configs_helper.rb @@ -1,12 +1,14 @@ 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. 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] ||= {} @@ -36,6 +38,7 @@ module Admin::ConfigsHelper # @see config_input # @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 config_input_field_options form, key, options config_input_tooltip_options form, key, options if options[:as] == :boolean diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index 71d9426c..5878470c 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -130,7 +130,7 @@ class FoodsoftConfig # @return [Boolean] Whether this key may be set in the database def allowed_key?(key) # fast check for keys without nesting - return !self.config[:protected].keys.include?(key) + return !self.config[:protected].keys.include?(key.to_s) # @todo allow to check nested keys as well end From 34e8901d040e28a16245c10ae976efd2acb8a6d2 Mon Sep 17 00:00:00 2001 From: wvengen Date: Mon, 23 Jun 2014 10:10:21 +0200 Subject: [PATCH 05/15] let plugins define their own foodcoop-config defaults --- lib/foodsoft_config.rb | 59 +++++++++++++++---- .../_tab_messages/add_config.html.haml.deface | 2 +- .../lib/foodsoft_messages/engine.rb | 4 ++ lib/foodsoft_wiki/lib/foodsoft_wiki/engine.rb | 4 ++ 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index 5878470c..6d224279 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -36,6 +36,8 @@ class FoodsoftConfig class << self def init + # Gather program-default configuration + self.default_config = get_default_config # Load initial config from development or production set_config Rails.env # Overwrite scope to have a better namescope than 'production' @@ -134,6 +136,40 @@ class FoodsoftConfig # @todo allow to check nested keys as well end + + protected + + # @!attribute default_config + # Returns the program-default foodcoop configuration. + # + # Plugins (engines in Rails terms) can easily add to the default + # configuration by defining a method +default_foodsoft_config+ in + # their engine and modify the {Hash} passed. + # + # When modifying this, please make sure to use default values that + # match old behaviour. For example, when the wiki was made optional + # and turned into a plugin, the configuration item +use_wiki+ was + # introduced with a default value of +true+ (set in the wiki plugin): + # + # module FoodsoftWiki + # class Engine < ::Rails::Engine + # def default_foodsoft_config(cfg) + # cfg[:use_wiki] = true # keep backward compatibility + # end + # end + # end + # + # @return [Hash] Default configuration values + mattr_accessor :default_config + + # Reload original configuration file, e.g. in between tests. + # @param filename [String] Override configuration file + def reload!(filename = APP_CONFIG_FILE) + APP_CONFIG.clear.merge! YAML.load(File.read(File.expand_path(filename, Rails.root))) + init + end + + private def set_config(foodcoop) @@ -152,11 +188,16 @@ class FoodsoftConfig # When new options are introduced, put backward-compatible defaults here, so that # configuration files that haven't been updated, still work as they did. This also # makes sure that the configuration editor picks up the defaults. + # @see #default_foodsoft_config def set_missing - config.replace({ + config.replace(default_config.merge(config)) + end + + # @return [Hash] Program-default foodcoop configuration. + # @see #default_config + def get_default_config + cfg = { use_nick: true, - use_wiki: true, - use_messages: true, use_apple_points: true, # English is the default language, and this makes it show up as default. default_locale: 'en', @@ -170,13 +211,11 @@ class FoodsoftConfig protected: nil, database: nil } - }.merge(config)) - end - - # reload original configuration file, e.g. in between tests - def reload!(filename = APP_CONFIG_FILE) - APP_CONFIG.clear.merge! YAML.load(File.read(File.expand_path(filename, Rails.root))) - init + } + # allow engines to easily add to this + engines = Rails.application.railties.engines.select { |e| e.respond_to?(:default_foodsoft_config) } + engines.each { |e| e.default_foodsoft_config(cfg) } + cfg end end diff --git a/lib/foodsoft_messages/app/overrides/admin/configs/_tab_messages/add_config.html.haml.deface b/lib/foodsoft_messages/app/overrides/admin/configs/_tab_messages/add_config.html.haml.deface index a948d4fd..41af5566 100644 --- a/lib/foodsoft_messages/app/overrides/admin/configs/_tab_messages/add_config.html.haml.deface +++ b/lib/foodsoft_messages/app/overrides/admin/configs/_tab_messages/add_config.html.haml.deface @@ -1,4 +1,4 @@ -/ insert_bottom ':root' +/ insert_bottom ':root:last-child' = config_use_heading form, :use_messages, label: 'Messages' do = config_input form, :mailing_list, as: :string, input_html: {class: 'input-xlarge'} = config_input form, :mailing_list_subscribe, as: :string, input_html: {class: 'input-xlarge'} diff --git a/lib/foodsoft_messages/lib/foodsoft_messages/engine.rb b/lib/foodsoft_messages/lib/foodsoft_messages/engine.rb index 38b8b4ff..81b063bb 100644 --- a/lib/foodsoft_messages/lib/foodsoft_messages/engine.rb +++ b/lib/foodsoft_messages/lib/foodsoft_messages/engine.rb @@ -11,5 +11,9 @@ module FoodsoftMessages sub_nav.items.insert(i, sub_nav.items.delete_at(-1)) end end + + def default_foodsoft_config(cfg) + cfg[:use_messages] = true + end end end diff --git a/lib/foodsoft_wiki/lib/foodsoft_wiki/engine.rb b/lib/foodsoft_wiki/lib/foodsoft_wiki/engine.rb index b56e617a..d74db4d2 100644 --- a/lib/foodsoft_wiki/lib/foodsoft_wiki/engine.rb +++ b/lib/foodsoft_wiki/lib/foodsoft_wiki/engine.rb @@ -11,5 +11,9 @@ module FoodsoftWiki primary.items.insert(i+1, primary.items.delete_at(-1)) end end + + def default_foodsoft_config(cfg) + cfg[:use_wiki] = true + end end end From 9beaac2627b6fa19c880e675645957026de3b70e Mon Sep 17 00:00:00 2001 From: wvengen Date: Mon, 23 Jun 2014 23:58:44 +0200 Subject: [PATCH 06/15] allow to specify (un)checked values for config boolean Conflicts: app/helpers/admin/configs_helper.rb --- app/helpers/admin/configs_helper.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/helpers/admin/configs_helper.rb b/app/helpers/admin/configs_helper.rb index 4f671fb7..e2ce7a0d 100644 --- a/app/helpers/admin/configs_helper.rb +++ b/app/helpers/admin/configs_helper.rb @@ -16,8 +16,8 @@ module Admin::ConfigsHelper config_input_tooltip_options form, key, options[:input_html] if options[:as] == :boolean options[:input_html][:checked] = 'checked' if options[:input_html].delete(:value) - options[:checked_value] = true - options[:unchecked_value] = 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 @@ -36,14 +36,18 @@ module Admin::ConfigsHelper # @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 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 options.delete(:value) - form.hidden_field(key, value: false, as: :hidden) + form.check_box(key, options, true, 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 From 6115979baefdeb275e444b7b7b2fca354790bb80 Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 25 Jun 2014 16:21:36 +0200 Subject: [PATCH 07/15] allow protected keys to be set/unset + config_db tests --- app/helpers/admin/configs_helper.rb | 1 + config/environments/test.rb | 3 + lib/foodsoft_config.rb | 53 +++++++++++++----- spec/app_config.yml | 3 + spec/integration/config_spec.rb | 45 +++++++++++++++ spec/lib/foodsoft_config_spec.rb | 85 +++++++++++++++++++++++++++++ spec/spec_helper.rb | 2 + 7 files changed, 179 insertions(+), 13 deletions(-) create mode 100644 spec/integration/config_spec.rb create mode 100644 spec/lib/foodsoft_config_spec.rb diff --git a/app/helpers/admin/configs_helper.rb b/app/helpers/admin/configs_helper.rb index e2ce7a0d..056f5cff 100644 --- a/app/helpers/admin/configs_helper.rb +++ b/app/helpers/admin/configs_helper.rb @@ -41,6 +41,7 @@ module Admin::ConfigsHelper # @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 diff --git a/config/environments/test.rb b/config/environments/test.rb index f389ad59..f0349f7c 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -7,6 +7,9 @@ Foodsoft::Application.configure do # 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. diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index 6d224279..4d4c67db 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -8,6 +8,26 @@ # In addition to the configuration file, values can be overridden in the database # using {RailsSettings::CachedSettings} as +foodcoop..**+. # +# Some values may not be set in the database (e.g. the database connection to +# sharedlists, or +default_scope+), these are defined as children of the +# +protected+ key. The default contains a sensible list, but you can modify +# that. Here's an almost minimal example: +# +# default: +# default_scope: f +# host: order.foodstuff.test # hostname for urls in emails +# +# name: Fairy Foodstuff # the name of our foodcoop +# contact: +# # ... +# email: fairy@foodstuff.test # general contact email address +# +# price_markup: 6 # foodcoop margin +# +# protected: +# shared_lists: false # allow database connection override +# use_messages: true # foodcoops can't disable the use of messages +# class FoodsoftConfig # @!attribute scope @@ -132,10 +152,14 @@ class FoodsoftConfig # @return [Boolean] Whether this key may be set in the database def allowed_key?(key) # fast check for keys without nesting - return !self.config[:protected].keys.include?(key.to_s) + return !self.config[:protected][key] # @todo allow to check nested keys as well end + # @return [Hash] Full configuration. + def to_hash + Hash[keys.map {|k| [k, self[k]]} ] + end protected @@ -185,16 +209,19 @@ class FoodsoftConfig ActiveRecord::Base.establish_connection(database_config) end - # When new options are introduced, put backward-compatible defaults here, so that - # configuration files that haven't been updated, still work as they did. This also - # makes sure that the configuration editor picks up the defaults. - # @see #default_foodsoft_config + # Completes foodcoop configuration with program defaults. + # @see #foodsoft_config def set_missing - config.replace(default_config.merge(config)) + config.replace(default_config.deep_merge(config)) end + # Returns program-default configuration. + # When new options are introduced, put backward-compatible defaults here, so that + # configuration files that haven't been updated, still work as they did. This also + # makes sure that the configuration editor picks up the defaults. # @return [Hash] Program-default foodcoop configuration. # @see #default_config + # @see #set_missing def get_default_config cfg = { use_nick: true, @@ -202,14 +229,14 @@ class FoodsoftConfig # English is the default language, and this makes it show up as default. default_locale: 'en', foodsoft_url: 'https://github.com/foodcoops/foodsoft', - # The following keys cannot be set by foodcoops themselves. + # The following keys cannot, by default, be set by foodcoops themselves. protected: { - multi_coop_install: nil, - default_scope: nil, - notification: nil, - shared_lists: nil, - protected: nil, - database: nil + multi_coop_install: true, + default_scope: true, + notification: true, + shared_lists: true, + protected: true, + database: true } } # allow engines to easily add to this diff --git a/spec/app_config.yml b/spec/app_config.yml index f9d0ed81..5da8aa3c 100644 --- a/spec/app_config.yml +++ b/spec/app_config.yml @@ -13,6 +13,9 @@ default: &defaults contact: email: fc@minimal.test + # required by configuration form (but otherwise not) + homepage: http://www.minimal.test/ + # true by default to keep compat with older installations, but test with false here use_nick: false diff --git a/spec/integration/config_spec.rb b/spec/integration/config_spec.rb new file mode 100644 index 00000000..d62f0894 --- /dev/null +++ b/spec/integration/config_spec.rb @@ -0,0 +1,45 @@ +require_relative '../spec_helper' + +describe 'admin/configs', type: :feature do + let(:name) { Faker::Lorem.words(rand(2..4)).join(' ') } + + describe type: :feature, js: true do + let(:admin) { create :admin } + before { login admin } + + it 'has initial value' do + FoodsoftConfig[:name] = name + visit admin_config_path + within('form.config') do + expect(find_field('config_name').value).to eq name + end + end + + it 'can modify a value' do + visit admin_config_path + fill_in 'config_name', with: name + within('form.config') do + find('input[type="submit"]').click + expect(find_field('config_name').value).to eq name + end + expect(FoodsoftConfig[:name]).to eq name + end + + it 'keeps config the same without changes' do + orig_values = get_full_config + visit admin_config_path + within('form.config') do + find('input[type="submit"]').click + expect(find_field('config_name').value).to eq FoodsoftConfig[:name] + end + expect(get_full_config).to eq orig_values + end + + def get_full_config + cfg = FoodsoftConfig.to_hash.deep_dup + cfg.each {|k,v| v.reject! {|k,v| v.blank?} if v.is_a? Hash} + cfg + end + + end +end diff --git a/spec/lib/foodsoft_config_spec.rb b/spec/lib/foodsoft_config_spec.rb new file mode 100644 index 00000000..6b35a3c2 --- /dev/null +++ b/spec/lib/foodsoft_config_spec.rb @@ -0,0 +1,85 @@ +require_relative '../spec_helper' + +describe FoodsoftConfig do + let(:name) { Faker::Lorem.words(rand(2..4)).join(' ') } + let(:other_name) { Faker::Lorem.words(rand(2..4)).join(' ') } + + it 'returns a default value' do + expect(FoodsoftConfig[:protected][:database]).to be_true + end + + it 'returns an empty default value' do + expect(FoodsoftConfig[:protected][:LIUhniuyGNKUQTWfbiOQIWYexngo78hqexul]).to be_false + end + + it 'returns a configuration value' do + FoodsoftConfig.config[:name] = name + expect(FoodsoftConfig[:name]).to eq name + end + + it 'can set a configuration value' do + FoodsoftConfig[:name] = name + expect(FoodsoftConfig[:name]).to eq name + end + + it 'can override a configuration value' do + FoodsoftConfig.config[:name] = name + FoodsoftConfig[:name] = other_name + expect(FoodsoftConfig[:name]).to eq other_name + end + + it 'cannot set a default protected value' do + old = FoodsoftConfig[:database] + FoodsoftConfig[:database] = name + expect(FoodsoftConfig.config[:database]).to eq old + end + + it 'can unprotect a default protected value' do + FoodsoftConfig.config[:protected][:database] = false + old = FoodsoftConfig[:database] + FoodsoftConfig[:database] = name + expect(FoodsoftConfig[:database]).to eq name + end + + describe 'can protect a value', type: :feature do + before do + FoodsoftConfig.config[:protected][:name] = true + end + + it 'can protect a value' do + old_name = FoodsoftConfig[:name] + FoodsoftConfig[:name] = name + expect(FoodsoftConfig[:name]).to eq old_name + end + + it 'and unprotect it again' do + old_name = FoodsoftConfig[:name] + FoodsoftConfig.config[:protected][:name] = false + FoodsoftConfig[:name] = name + expect(FoodsoftConfig[:name]).to eq name + end + end + + describe 'has indifferent access', type: :feature do + it 'with symbol' do + FoodsoftConfig[:name] = name + expect(FoodsoftConfig[:name]).to eq FoodsoftConfig['name'] + end + + it 'with string' do + FoodsoftConfig['name'] = name + expect(FoodsoftConfig['name']).to eq FoodsoftConfig[:name] + end + + it 'with nested symbol' do + FoodsoftConfig[:protected][:database] = true + expect(FoodsoftConfig[:protected]['database']).to eq FoodsoftConfig[:protected][:database] + end + + it 'with nested string' do + FoodsoftConfig[:protected]['database'] = true + expect(FoodsoftConfig[:protected]['database']).to eq FoodsoftConfig[:protected][:database] + end + end + +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 739df522..3b7389c0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -35,6 +35,8 @@ RSpec.configure do |config| end config.after(:each) do DatabaseCleaner.clean + # Need to clear cache for RailsSettings::CachedSettings + Rails.cache.clear end # reload foodsoft configuration, so that tests can use FoodsoftConfig.config[:foo]=x From daae87f6d56e4fb78da0ff8b70560dda47392b9d Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 25 Jun 2014 16:28:08 +0200 Subject: [PATCH 08/15] fix deprecation warning --- lib/foodsoft_config.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index 4d4c67db..3f377586 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -240,7 +240,7 @@ class FoodsoftConfig } } # allow engines to easily add to this - engines = Rails.application.railties.engines.select { |e| e.respond_to?(:default_foodsoft_config) } + engines = Rails::Engine::Railties.engines.select { |e| e.respond_to?(:default_foodsoft_config) } engines.each { |e| e.default_foodsoft_config(cfg) } cfg end From 3fee071a1090cc14671dacacbb69ebb0338bdef1 Mon Sep 17 00:00:00 2001 From: wvengen Date: Wed, 25 Jun 2014 17:32:26 +0200 Subject: [PATCH 09/15] specify timezone in configuration (closes foodcoops#282) --- app/controllers/application_controller.rb | 14 +++++++++++++- app/helpers/admin/configs_helper.rb | 4 ++++ app/views/admin/configs/_tab_language.html.haml | 1 + config/app_config.yml.SAMPLE | 2 ++ config/locales/en.yml | 1 + config/locales/nl.yml | 1 + 6 files changed, 22 insertions(+), 1 deletion(-) diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 3574ef44..6c14f1c9 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -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 # Returns the controller handling the current request. @@ -167,5 +168,16 @@ class ApplicationController < ActionController::Base 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 + end diff --git a/app/helpers/admin/configs_helper.rb b/app/helpers/admin/configs_helper.rb index 056f5cff..68d759fc 100644 --- a/app/helpers/admin/configs_helper.rb +++ b/app/helpers/admin/configs_helper.rb @@ -7,6 +7,7 @@ module Admin::ConfigsHelper # @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) @@ -21,6 +22,9 @@ module Admin::ConfigsHelper 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 diff --git a/app/views/admin/configs/_tab_language.html.haml b/app/views/admin/configs/_tab_language.html.haml index 4debea6a..c2c956a4 100644 --- a/app/views/admin/configs/_tab_language.html.haml +++ b/app/views/admin/configs/_tab_language.html.haml @@ -1,3 +1,4 @@ = 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'} diff --git a/config/app_config.yml.SAMPLE b/config/app_config.yml.SAMPLE index fbb4731e..a485c6e2 100644 --- a/config/app_config.yml.SAMPLE +++ b/config/app_config.yml.SAMPLE @@ -37,6 +37,8 @@ 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 # price markup in percent price_markup: 2.0 diff --git a/config/locales/en.yml b/config/locales/en.yml index 79e956b7..88374665 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -504,6 +504,7 @@ en: pdf_font_size: Font size pdf_page_size: Page size pdf_add_page_breaks: Page breaks + time_zone: Time zone deliveries: add_stock_change: how_many_units: 'How many units (%{unit}) to deliver? Stock article name: %{name}.' diff --git a/config/locales/nl.yml b/config/locales/nl.yml index 56313c7a..a53baea2 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -487,6 +487,7 @@ nl: pdf_font_size: Tekstgrootte pdf_page_size: Paginaformaat pdf_add_page_breaks: "Nieuwe pagina's" + time_zone: Tijdzone deliveries: add_stock_change: how_many_units: 'Hoeveel eenheden (%{unit}) leveren? Voorraadartikel: %{name}.' From f09ef892dc8e8bce123edb2296ef72927d47cbda Mon Sep 17 00:00:00 2001 From: wvengen Date: Fri, 27 Jun 2014 09:07:47 +0200 Subject: [PATCH 10/15] add foodsoft_config protection whitelisting --- lib/foodsoft_config.rb | 9 ++++++++- spec/lib/foodsoft_config_spec.rb | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index 3f377586..d6d8257c 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -28,6 +28,9 @@ # shared_lists: false # allow database connection override # use_messages: true # foodcoops can't disable the use of messages # +# When you like to whitelist protected attributes, define an entry +all: true+, +# then you can whitelist specific attributes setting them to +false+. +# class FoodsoftConfig # @!attribute scope @@ -152,7 +155,11 @@ class FoodsoftConfig # @return [Boolean] Whether this key may be set in the database def allowed_key?(key) # fast check for keys without nesting - return !self.config[:protected][key] + if self.config[:protected].include? key + return !self.config[:protected][key] + else + return !self.config[:protected][:all] + end # @todo allow to check nested keys as well end diff --git a/spec/lib/foodsoft_config_spec.rb b/spec/lib/foodsoft_config_spec.rb index 6b35a3c2..20ab28f5 100644 --- a/spec/lib/foodsoft_config_spec.rb +++ b/spec/lib/foodsoft_config_spec.rb @@ -5,11 +5,11 @@ describe FoodsoftConfig do let(:other_name) { Faker::Lorem.words(rand(2..4)).join(' ') } it 'returns a default value' do - expect(FoodsoftConfig[:protected][:database]).to be_true + expect(FoodsoftConfig[:protected][:database]).to be true end it 'returns an empty default value' do - expect(FoodsoftConfig[:protected][:LIUhniuyGNKUQTWfbiOQIWYexngo78hqexul]).to be_false + expect(FoodsoftConfig[:protected][:LIUhniuyGNKUQTWfbiOQIWYexngo78hqexul]).to be nil end it 'returns a configuration value' do @@ -60,6 +60,20 @@ describe FoodsoftConfig do end end + it 'can protect all values' do + old_name = FoodsoftConfig[:name] + FoodsoftConfig.config[:protected][:all] = true + FoodsoftConfig[:name] = name + expect(FoodsoftConfig[:name]).to eq old_name + end + + it 'can whitelist a value' do + FoodsoftConfig.config[:protected][:all] = true + FoodsoftConfig.config[:protected][:name] = false + FoodsoftConfig[:name] = name + expect(FoodsoftConfig[:name]).to eq name + end + describe 'has indifferent access', type: :feature do it 'with symbol' do FoodsoftConfig[:name] = name From 68ccc19c70981157234802cdbc136c82f5a956d2 Mon Sep 17 00:00:00 2001 From: wvengen Date: Fri, 27 Jun 2014 11:45:34 +0200 Subject: [PATCH 11/15] allow to configure currency (closes foodcoops#258) --- app/controllers/application_controller.rb | 14 +++++++++++++- app/views/admin/configs/_tab_language.html.haml | 5 +++++ config/app_config.yml.SAMPLE | 3 +++ config/initializers/currency_display.rb | 7 +++++++ config/locales/en.yml | 4 ++++ lib/foodsoft_config.rb | 2 ++ 6 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 config/initializers/currency_display.rb diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 6c14f1c9..3a01550a 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -6,7 +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 + around_filter :set_time_zone, :set_currency # Returns the controller handling the current request. @@ -180,4 +180,16 @@ class ApplicationController < ActionController::Base 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 diff --git a/app/views/admin/configs/_tab_language.html.haml b/app/views/admin/configs/_tab_language.html.haml index c2c956a4..55cb3cfd 100644 --- a/app/views/admin/configs/_tab_language.html.haml +++ b/app/views/admin/configs/_tab_language.html.haml @@ -2,3 +2,8 @@ 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 diff --git a/config/app_config.yml.SAMPLE b/config/app_config.yml.SAMPLE index a485c6e2..a455f113 100644 --- a/config/app_config.yml.SAMPLE +++ b/config/app_config.yml.SAMPLE @@ -39,6 +39,9 @@ default: &defaults #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 diff --git a/config/initializers/currency_display.rb b/config/initializers/currency_display.rb new file mode 100644 index 00000000..191f255c --- /dev/null +++ b/config/initializers/currency_display.rb @@ -0,0 +1,7 @@ +# remove all currency translations, so that we can set the default language and +# have it shown in all other languages too +::I18n.available_locales.each do |locale| + unless locale == ::I18n.default_locale + ::I18n.backend.store_translations(locale, number: {currency: {format: {unit: nil}}}) + end +end diff --git a/config/locales/en.yml b/config/locales/en.yml index 88374665..43ba4e7d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -450,6 +450,8 @@ en: contact: email: General contact email address, shown on website as well as some forms. street: Address, typically this will be your delivery and pick-up location. + currency_unit: Currency symbol for displaying prices. + currency_space: Whether to add whitespace after the currency symbol. homepage: Website of your foodcoop. help_url: Documentation website. applepear_url: Website where the apple and pear system for tasks is explained. @@ -481,6 +483,8 @@ en: country: Country email: Email phone: Phone + currency_unit: Currency + currency_space: add space homepage: Homepage help_url: Documentation URL applepear_url: Apple system URL diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index d6d8257c..eb238a7a 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -235,6 +235,8 @@ class FoodsoftConfig use_apple_points: true, # English is the default language, and this makes it show up as default. default_locale: 'en', + currency_unit: '€', + currency_space: true, foodsoft_url: 'https://github.com/foodcoops/foodsoft', # The following keys cannot, by default, be set by foodcoops themselves. protected: { From 0f7dc3701516111a0640c276522125f79f89355b Mon Sep 17 00:00:00 2001 From: wvengen Date: Mon, 1 Sep 2014 14:07:53 +0200 Subject: [PATCH 12/15] fix config value override Conflicts: app/helpers/admin/configs_helper.rb --- app/helpers/admin/configs_helper.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/helpers/admin/configs_helper.rb b/app/helpers/admin/configs_helper.rb index 68d759fc..370b36c4 100644 --- a/app/helpers/admin/configs_helper.rb +++ b/app/helpers/admin/configs_helper.rb @@ -121,9 +121,11 @@ module Admin::ConfigsHelper def config_input_field_options(form, key, options) cfg_path = form.lookup_model_names[1..-1] + [key] # set current value - value = @cfg - cfg_path.each {|n| value = value[n] unless value.nil? } - options[:value] ||= 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 From cf680d341055c8978b572e7bca08c81349a8b882 Mon Sep 17 00:00:00 2001 From: wvengen Date: Mon, 1 Sep 2014 14:24:24 +0200 Subject: [PATCH 13/15] add page break to config screen --- app/helpers/admin/configs_helper.rb | 4 ++-- app/views/admin/configs/_tab_layout.html.haml | 9 ++++++++- config/locales/en.yml | 4 +++- config/locales/nl.yml | 4 +++- 4 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/helpers/admin/configs_helper.rb b/app/helpers/admin/configs_helper.rb index 370b36c4..37e16185 100644 --- a/app/helpers/admin/configs_helper.rb +++ b/app/helpers/admin/configs_helper.rb @@ -16,7 +16,7 @@ module Admin::ConfigsHelper 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 options[:input_html].delete(:value) + 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 @@ -51,7 +51,7 @@ module Admin::ConfigsHelper if options[:as] == :boolean checked_value = options.delete(:checked_value) || 'true' unchecked_value = options.delete(:unchecked_value) || 'false' - options[:checked] = 'checked' if options.delete(:value) + 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 diff --git a/app/views/admin/configs/_tab_layout.html.haml b/app/views/admin/configs/_tab_layout.html.haml index 8d37da66..f706d917 100644 --- a/app/views/admin/configs/_tab_layout.html.haml +++ b/app/views/admin/configs/_tab_layout.html.haml @@ -4,4 +4,11 @@ .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, as: :boolean += 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' diff --git a/config/locales/en.yml b/config/locales/en.yml index 43ba4e7d..842395cd 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -472,7 +472,9 @@ en: page_footer: 'Shown on each page at the bottom. Enter "blank" to disable the footer completely.' pdf_font_size: Base font size for PDF documents (12 is standard). pdf_page_size: 'Page size for PDF documents, typically "A4" or "letter".' - pdf_add_page_breaks: Add page breaks when starting a new section. + pdf_add_page_breaks: + order_by_articles: Put each article on a separate page. + order_by_groups: Put each ordergroup on a separate page. tolerance_is_costly: "Order as much of the member tolerance as possible (compared to only as much needed to fill the last box). Enabling this also includes the tolerance in the total price of the open member order." keys: name: Name diff --git a/config/locales/nl.yml b/config/locales/nl.yml index a53baea2..d70dc069 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -453,7 +453,9 @@ nl: page_footer: 'Wordt op iedere pagina getoond. Vul "blank" in om de voettekst helemaal weg te halen.' pdf_font_size: Basis tekstgrootte voor PDF bestanden (standaard 12). pdf_page_size: 'Paginaformaat voor PDF bestanden, meestal "A4" of "letter".' - pdf_add_page_breaks: Secties op een nieuwe pagina beginnen. + pdf_add_page_breaks: + order_by_articles: Ieder artikel op een nieuwe pagina. + order_by_groups: Ieder huishouden op een nieuwe pagina. tolerance_is_costly: "Bestel zoveel artikelen als mogelijk in de tolerantie (in plaats van net genoeg om de laatste doos te vullen). Dit zorgt er ook voor dat de tolerantie in de prijs van open ledenbestellingen wordt meegenomen." keys: name: Naam From 1a3b690757f94968ed28519274415e72ebf8da3a Mon Sep 17 00:00:00 2001 From: wvengen Date: Tue, 2 Sep 2014 14:15:34 +0200 Subject: [PATCH 14/15] add custom css to config screen --- app/views/admin/configs/_tab_layout.html.haml | 1 + config/locales/en.yml | 2 ++ config/locales/nl.yml | 2 ++ 3 files changed, 5 insertions(+) diff --git a/app/views/admin/configs/_tab_layout.html.haml b/app/views/admin/configs/_tab_layout.html.haml index f706d917..b27882d8 100644 --- a/app/views/admin/configs/_tab_layout.html.haml +++ b/app/views/admin/configs/_tab_layout.html.haml @@ -1,4 +1,5 @@ = 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 diff --git a/config/locales/en.yml b/config/locales/en.yml index 842395cd..db2e37a0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -452,6 +452,7 @@ en: street: Address, typically this will be your delivery and pick-up location. currency_unit: Currency symbol for displaying prices. currency_space: Whether to add whitespace after the currency symbol. + custom_css: To modify the layout of this site, you can enter style modifications using the cascading stylesheets (CSS) language. Leave blank for the default style. homepage: Website of your foodcoop. help_url: Documentation website. applepear_url: Website where the apple and pear system for tasks is explained. @@ -487,6 +488,7 @@ en: phone: Phone currency_unit: Currency currency_space: add space + custom_css: Custom CSS homepage: Homepage help_url: Documentation URL applepear_url: Apple system URL diff --git a/config/locales/nl.yml b/config/locales/nl.yml index d70dc069..1c998c6c 100644 --- a/config/locales/nl.yml +++ b/config/locales/nl.yml @@ -433,6 +433,7 @@ nl: contact: email: Algemeen contactadres, zowel voor op de website als in formulieren. street: Adres, meestal is dit het aflever- en ophaaladres. + custom_css: De layout van deze site kan gewijzigd worden door hier cascading stylesheets (CSS) in te voeren. Laat het leeg voor de standaardstijl. homepage: Website van de foodcoop. help_url: Documentatie website. applepear_url: Website waar het appelpunten systeem wordt uitgelegd. @@ -466,6 +467,7 @@ nl: country: Land email: Email phone: Telefoon + custom_css: Aangepaste CSS homepage: Homepage help_url: Documentatie URL applepear_url: Appelsysteem uitleg URL From 10a193add38617dcc80afa99de61605428611913 Mon Sep 17 00:00:00 2001 From: wvengen Date: Tue, 2 Sep 2014 14:43:45 +0200 Subject: [PATCH 15/15] make nested properties work better --- lib/foodsoft_config.rb | 25 ++++++++++++++++--------- spec/integration/config_spec.rb | 1 + 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/foodsoft_config.rb b/lib/foodsoft_config.rb index eb238a7a..1975f2c7 100644 --- a/lib/foodsoft_config.rb +++ b/lib/foodsoft_config.rb @@ -109,15 +109,7 @@ class FoodsoftConfig # @return [Boolean] Whether storing succeeded (fails when key is not allowed to be set in database). def []=(key, value) return false unless allowed_key?(key) - # try to figure out type ... - value = case value - when 'true' then true - when 'false' then false - when /^[-+0-9]+$/ then value.to_i - when /^[-+0-9.]+([eE][-+0-9]+)?$/ then value.to_f - when '' then nil - else value - end + value = normalize_value value # then update database if config[key] == value or (config[key].nil? and value == false) # delete (ok if it was already deleted) @@ -254,5 +246,20 @@ class FoodsoftConfig cfg end + # Normalize value recursively (which can be entered as strings, but we want to store it properly) + def normalize_value(value) + value = value.map(&:normalize_value) if value.is_a? Array + value = Hash[ value.to_a.map{|a| [a[0], normalize_value(a[1])]} ] if value.is_a? Hash + case value + when 'true' then true + when 'false' then false + when /^[-+0-9]+$/ then value.to_i + when /^[-+0-9.]+([eE][-+0-9]+)?$/ then value.to_f + when '' then nil + else value + end + end + + end end diff --git a/spec/integration/config_spec.rb b/spec/integration/config_spec.rb index d62f0894..ee7b2956 100644 --- a/spec/integration/config_spec.rb +++ b/spec/integration/config_spec.rb @@ -38,6 +38,7 @@ describe 'admin/configs', type: :feature do def get_full_config cfg = FoodsoftConfig.to_hash.deep_dup cfg.each {|k,v| v.reject! {|k,v| v.blank?} if v.is_a? Hash} + cfg.reject! {|k,v| v.blank?} cfg end