diff --git a/app/controllers/admin/budget_headings_controller.rb b/app/controllers/admin/budget_headings_controller.rb index 902f256b5..4e1bf6ed2 100644 --- a/app/controllers/admin/budget_headings_controller.rb +++ b/app/controllers/admin/budget_headings_controller.rb @@ -33,7 +33,7 @@ class Admin::BudgetHeadingsController < Admin::BaseController private def budget_heading_params - params.require(:budget_heading).permit(:name, :price, :population) + params.require(:budget_heading).permit(:name, :price, :population, :allow_custom_content) end end diff --git a/app/controllers/admin/site_customization/content_blocks_controller.rb b/app/controllers/admin/site_customization/content_blocks_controller.rb index 1697cb76f..2fb38c018 100644 --- a/app/controllers/admin/site_customization/content_blocks_controller.rb +++ b/app/controllers/admin/site_customization/content_blocks_controller.rb @@ -1,12 +1,23 @@ class Admin::SiteCustomization::ContentBlocksController < Admin::SiteCustomization::BaseController - load_and_authorize_resource :content_block, class: "SiteCustomization::ContentBlock" + load_and_authorize_resource :content_block, class: "SiteCustomization::ContentBlock", + except: [:delete_heading_content_block, :edit_heading_content_block, :update_heading_content_block] def index @content_blocks = SiteCustomization::ContentBlock.order(:name, :locale) + @headings_content_blocks = Budget::ContentBlock.all end def create - if @content_block.save + if is_heading_content_block?(@content_block.name) + heading_content_block = new_heading_content_block + if heading_content_block.save + notice = t('admin.site_customization.content_blocks.create.notice') + redirect_to admin_site_customization_content_blocks_path, notice: notice + else + flash.now[:error] = t('admin.site_customization.content_blocks.create.error') + render :new + end + elsif @content_block.save notice = t('admin.site_customization.content_blocks.create.notice') redirect_to admin_site_customization_content_blocks_path, notice: notice else @@ -15,8 +26,22 @@ class Admin::SiteCustomization::ContentBlocksController < Admin::SiteCustomizati end end + def edit + @selected_content_block = (@content_block.is_a? SiteCustomization::ContentBlock) ? @content_block.name : "hcb_#{ @content_block.heading_id }" + end + def update - if @content_block.update(content_block_params) + if is_heading_content_block?(params[:site_customization_content_block][:name]) + heading_content_block = new_heading_content_block + if heading_content_block.save + @content_block.destroy + notice = t('admin.site_customization.content_blocks.create.notice') + redirect_to admin_site_customization_content_blocks_path, notice: notice + else + flash.now[:error] = t('admin.site_customization.content_blocks.create.error') + render :new + end + elsif @content_block.update(content_block_params) notice = t('admin.site_customization.content_blocks.update.notice') redirect_to admin_site_customization_content_blocks_path, notice: notice else @@ -31,6 +56,48 @@ class Admin::SiteCustomization::ContentBlocksController < Admin::SiteCustomizati redirect_to admin_site_customization_content_blocks_path, notice: notice end + def delete_heading_content_block + heading_content_block = Budget::ContentBlock.find(params[:id]) + heading_content_block.destroy if heading_content_block + notice = t('admin.site_customization.content_blocks.destroy.notice') + redirect_to admin_site_customization_content_blocks_path, notice: notice + end + + def edit_heading_content_block + @content_block = Budget::ContentBlock.find(params[:id]) + @selected_content_block = (@content_block.is_a? Budget::ContentBlock) ? "hcb_#{ @content_block.heading_id }" : @content_block.heading.name + @is_heading_content_block = true + render :edit + end + + def update_heading_content_block + heading_content_block = Budget::ContentBlock.find(params[:id]) + if is_heading_content_block?(params[:name]) + heading_content_block.locale = params[:locale] + heading_content_block.body = params[:body] + if heading_content_block.save + notice = t('admin.site_customization.content_blocks.update.notice') + redirect_to admin_site_customization_content_blocks_path, notice: notice + else + flash.now[:error] = t('admin.site_customization.content_blocks.update.error') + render :edit + end + else + @content_block = SiteCustomization::ContentBlock.new + @content_block.name = params[:name] + @content_block.locale = params[:locale] + @content_block.body = params[:body] + if @content_block.save + heading_content_block.destroy + notice = t('admin.site_customization.content_blocks.update.notice') + redirect_to admin_site_customization_content_blocks_path, notice: notice + else + flash.now[:error] = t('admin.site_customization.content_blocks.update.error') + render :edit + end + end + end + private def content_block_params @@ -40,4 +107,16 @@ class Admin::SiteCustomization::ContentBlocksController < Admin::SiteCustomizati :body ) end + + def is_heading_content_block?(name) + name.start_with?('hcb_') + end + + def new_heading_content_block + heading_content_block = Budget::ContentBlock.new + heading_content_block.body = params[:site_customization_content_block][:body] + heading_content_block.locale = params[:site_customization_content_block][:locale] + heading_content_block.heading_id = params[:site_customization_content_block][:name].sub('hcb_', '').to_i + heading_content_block + end end diff --git a/app/controllers/budgets/investments_controller.rb b/app/controllers/budgets/investments_controller.rb index ca2bf7dc4..b048cc4f5 100644 --- a/app/controllers/budgets/investments_controller.rb +++ b/app/controllers/budgets/investments_controller.rb @@ -17,6 +17,7 @@ module Budgets before_action :load_categories, only: [:index, :new, :create] before_action :set_default_budget_filter, only: :index before_action :set_view, only: :index + before_action :load_content_blocks, only: :index skip_authorization_check only: :json_data @@ -149,6 +150,10 @@ module Budgets @categories = ActsAsTaggableOn::Tag.category.order(:name) end + def load_content_blocks + @heading_content_blocks = @heading.content_blocks.where(locale: I18n.locale) if @heading + end + def tag_cloud TagCloud.new(Budget::Investment, params[:search]) end diff --git a/app/helpers/content_blocks_helper.rb b/app/helpers/content_blocks_helper.rb new file mode 100644 index 000000000..a2e968c08 --- /dev/null +++ b/app/helpers/content_blocks_helper.rb @@ -0,0 +1,9 @@ +module ContentBlocksHelper + def valid_blocks + options = SiteCustomization::ContentBlock::VALID_BLOCKS.map { |key| [t("admin.site_customization.content_blocks.content_block.names.#{key}"), key] } + Budget::Heading.allow_custom_content.each do |heading| + options.push([heading.name, "hcb_#{heading.id}"]) + end + options + end +end diff --git a/app/models/budget/content_block.rb b/app/models/budget/content_block.rb new file mode 100644 index 000000000..bba830cec --- /dev/null +++ b/app/models/budget/content_block.rb @@ -0,0 +1,9 @@ +class Budget + class ContentBlock < ActiveRecord::Base + validates :locale, presence: true, inclusion: { in: I18n.available_locales.map(&:to_s) } + validates :heading, presence: true + validates_uniqueness_of :heading, scope: :locale + + belongs_to :heading + end +end diff --git a/app/models/budget/heading.rb b/app/models/budget/heading.rb index 4818eaeed..555240e6a 100644 --- a/app/models/budget/heading.rb +++ b/app/models/budget/heading.rb @@ -5,6 +5,7 @@ class Budget belongs_to :group has_many :investments + has_many :content_blocks validates :group_id, presence: true validates :name, presence: true, uniqueness: { if: :name_exists_in_budget_headings } @@ -15,6 +16,7 @@ class Budget delegate :budget, :budget_id, to: :group, allow_nil: true scope :order_by_group_name, -> { includes(:group).order('budget_groups.name', 'budget_headings.name') } + scope :allow_custom_content, -> { where(allow_custom_content: true).order(:name) } def name_scoped_by_group group.single_heading_group? ? name : "#{group.name}: #{name}" diff --git a/app/views/admin/budgets/_group.html.erb b/app/views/admin/budgets/_group.html.erb index c3a1c07a8..e4b313207 100644 --- a/app/views/admin/budgets/_group.html.erb +++ b/app/views/admin/budgets/_group.html.erb @@ -1,7 +1,7 @@ - + <% end %> @@ -26,7 +27,7 @@ <% if headings.blank? %> - - diff --git a/app/views/admin/budgets/_heading.html.erb b/app/views/admin/budgets/_heading.html.erb index 6cef11c9e..e06bb96d1 100644 --- a/app/views/admin/budgets/_heading.html.erb +++ b/app/views/admin/budgets/_heading.html.erb @@ -8,6 +8,9 @@ +
+ <%= content_tag(:span, group.name, class:"group-toggle-#{group.id}", id:"group-name-#{group.id}") %> <%= content_tag(:span, (render 'admin/budgets/max_headings_label', current: group.max_votable_headings, max: group.headings.count, group: group if group.max_votable_headings), class:"small group-toggle-#{group.id}", id:"max-heading-label-#{group.id}") %> @@ -17,6 +17,7 @@ <%= t("admin.budgets.form.table_heading") %> <%= t("admin.budgets.form.table_amount") %> <%= t("admin.budgets.form.table_population") %><%= t("admin.budgets.form.table_allow_custom_contents") %> <%= t("admin.actions.actions") %>
+
<%= t("admin.budgets.form.no_heading") %>
@@ -35,7 +36,7 @@ <% end %>
<%= heading.population %> + <%= heading.allow_custom_content ? t("true_value") : t("false_value") %> + <%= link_to t("admin.actions.edit"), edit_admin_budget_budget_group_budget_heading_path(budget_id: group.budget.id, budget_group_id: group.id, id: heading.id), diff --git a/app/views/admin/budgets/_heading_form.html.erb b/app/views/admin/budgets/_heading_form.html.erb index 5c50e21a8..acf534e6c 100644 --- a/app/views/admin/budgets/_heading_form.html.erb +++ b/app/views/admin/budgets/_heading_form.html.erb @@ -35,5 +35,11 @@ +
+
+ <%= f.check_box :allow_custom_content, label: t('admin.budgets.form.allow_content_block') %> +
+
+ <%= f.submit t("admin.budgets.form.save_heading"), class: "button success" %> <% end %> diff --git a/app/views/admin/site_customization/content_blocks/_form.html.erb b/app/views/admin/site_customization/content_blocks/_form.html.erb index 1286884bc..7f7799d2c 100644 --- a/app/views/admin/site_customization/content_blocks/_form.html.erb +++ b/app/views/admin/site_customization/content_blocks/_form.html.erb @@ -1,35 +1,5 @@ -<%= form_for [:admin, @content_block], html: {class: "edit_page", data: {watch_changes: true}} do |f| %> - - <% if @content_block.errors.any? %> - -
- - - - <%= @content_block.errors.count %> - <%= t("admin.site_customization.content_blocks.errors.form.error", count: @content_block.errors.count) %> - -
- - <% end %> - -
- <%= f.label :name %> - <%= f.select :name, SiteCustomization::ContentBlock::VALID_BLOCKS.map { |key| [t("admin.site_customization.content_blocks.content_block.names.#{key}"), key] }, label: false %> -
-
- <%= f.label :locale %> - <%= f.select :locale, I18n.available_locales, label: false %> -
- -
- <%= f.label :body %> - <%= f.text_area :body, label: false, rows: 10 %> -
- <%= f.submit class: "button success expanded" %> -
-
- +<% if @is_heading_content_block %> + <%= render 'form_heading_content_block' %> +<% else %> + <%= render 'form_content_block' %> <% end %> diff --git a/app/views/admin/site_customization/content_blocks/_form_content_block.html.erb b/app/views/admin/site_customization/content_blocks/_form_content_block.html.erb new file mode 100644 index 000000000..b54861dca --- /dev/null +++ b/app/views/admin/site_customization/content_blocks/_form_content_block.html.erb @@ -0,0 +1,35 @@ +<%= form_for [:admin, @content_block], html: {class: "edit_page", data: {watch_changes: true}} do |f| %> + + <% if @content_block.errors.any? %> + +
+ + + + <%= @content_block.errors.count %> + <%= t("admin.site_customization.content_blocks.errors.form.error", count: @content_block.errors.count) %> + +
+ + <% end %> + +
+ <%= f.label :name %> + <%= f.select :name, options_for_select(valid_blocks, @selected_content_block), label: false %> +
+
+ <%= f.label :locale %> + <%= f.select :locale, I18n.available_locales, label: false %> +
+ +
+ <%= f.label :body %> + <%= f.text_area :body, label: false, rows: 10 %> +
+ <%= f.submit class: "button success expanded" %> +
+
+ +<% end %> diff --git a/app/views/admin/site_customization/content_blocks/_form_heading_content_block.html.erb b/app/views/admin/site_customization/content_blocks/_form_heading_content_block.html.erb new file mode 100644 index 000000000..2d14cd0c9 --- /dev/null +++ b/app/views/admin/site_customization/content_blocks/_form_heading_content_block.html.erb @@ -0,0 +1,29 @@ +<%= form_tag(admin_site_customization_update_heading_content_block_path(@content_block.id), method: "put") do %> + <% if @content_block.errors.any? %> +
+ + + <%= @content_block.errors.count %> + <%= t("admin.site_customization.content_blocks.errors.form.error", count: @content_block.errors.count) %> + +
+ <% end %> + +
+ <%= label_tag :name %> + <%= select_tag :name, options_for_select(valid_blocks, @selected_content_block), label: false %> +
+
+ <%= label_tag :locale %> + <%= select_tag :locale, options_for_select(I18n.available_locales, @content_block.locale.to_sym), label: false %> +
+
+ <%= label_tag :body %> + <%= text_area_tag :body, @content_block.body, rows: 10 %> +
+ <%= button_tag t("admin.menu.site_customization.buttons.content_block.update"), class: "button success expanded" %> +
+
+<% end %> diff --git a/app/views/admin/site_customization/content_blocks/edit.html.erb b/app/views/admin/site_customization/content_blocks/edit.html.erb index d01e4a01b..c81a0d11c 100644 --- a/app/views/admin/site_customization/content_blocks/edit.html.erb +++ b/app/views/admin/site_customization/content_blocks/edit.html.erb @@ -1,11 +1,11 @@ <% provide :title do %> - <%= t("admin.header.title") %> - <%= t("admin.menu.site_customization.content_blocks") %> - <%= @content_block.name %> (<%= @content_block.locale %>) + <%= t("admin.header.title") %> - <%= t("admin.menu.site_customization.content_blocks") %> - <%= @content_block.try(:name) || @content_block.heading.try(:name) %> (<%= @content_block.locale %>) <% end %> <%= back_link_to admin_site_customization_content_blocks_path %> <%= link_to t("admin.site_customization.content_blocks.index.delete"), - admin_site_customization_content_block_path(@content_block), + (@is_heading_content_block ? admin_site_customization_delete_heading_content_block_path(@content_block.id) : admin_site_customization_content_block_path(@content_block)), method: :delete, class: "delete float-right" %> diff --git a/app/views/admin/site_customization/content_blocks/index.html.erb b/app/views/admin/site_customization/content_blocks/index.html.erb index 66cf5f241..d779b130c 100644 --- a/app/views/admin/site_customization/content_blocks/index.html.erb +++ b/app/views/admin/site_customization/content_blocks/index.html.erb @@ -17,7 +17,7 @@

<%= t("admin.site_customization.content_blocks.footer_html") %>

-<% if @content_blocks.any? %> +<% if @content_blocks.any? || @headings_content_blocks.any? %> @@ -38,10 +38,21 @@ <% end %> + <% @headings_content_blocks.each do |content_block| %> + + + + + + <% end %>
<%= link_to "#{content_block.heading.name} (#{content_block.locale})", admin_site_customization_edit_heading_content_block_path(content_block) %><%= content_block.body.html_safe %> + <%= link_to t("admin.site_customization.content_blocks.index.delete"), + admin_site_customization_delete_heading_content_block_path(content_block.id), + method: :delete, class: "button hollow alert" %> +
<% else %>
<%= t("admin.site_customization.content_blocks.no_blocks") %> -
+ <% end %> diff --git a/app/views/budgets/investments/_content_blocks.html.erb b/app/views/budgets/investments/_content_blocks.html.erb new file mode 100644 index 000000000..b5996bf7a --- /dev/null +++ b/app/views/budgets/investments/_content_blocks.html.erb @@ -0,0 +1,9 @@ +<% if @heading.allow_custom_content %> +
+ +
    + <% @heading_content_blocks.each do |content_block| %> + <%= raw content_block.body %> + <% end %> +
+<% end %> diff --git a/app/views/budgets/investments/_sidebar.html.erb b/app/views/budgets/investments/_sidebar.html.erb index c020c8bbe..da06fdcfa 100644 --- a/app/views/budgets/investments/_sidebar.html.erb +++ b/app/views/budgets/investments/_sidebar.html.erb @@ -21,9 +21,13 @@

<% end %> +<% if @heading && !@heading.content_blocks.where(locale: I18n.locale).empty? %> + <%= render 'budgets/investments/content_blocks' %> +<% end %> <%= render "shared/tag_cloud", taggable: 'budget/investment' %> <%= render 'budgets/investments/categories' %> + <% if @heading && can?(:show, @ballot) %> diff --git a/config/locales/en/admin.yml b/config/locales/en/admin.yml index 6ee435c55..df3e6c0b9 100644 --- a/config/locales/en/admin.yml +++ b/config/locales/en/admin.yml @@ -1,4 +1,6 @@ en: + true_value: 'Yes' + false_value: 'No' admin: header: title: Administration @@ -128,9 +130,11 @@ en: table_heading: Heading table_amount: Amount table_population: Population + table_allow_custom_contents: Custom content allowed population_info: "Budget Heading population field is used for Statistic purposes at the end of the Budget to show for each Heading that represents an area with population what percentage voted. The field is optional so you can leave it empty if it doesn't apply." max_votable_headings: "Maximum number of headings in which a user can vote" current_of_max_headings: "%{current} of %{max}" + allow_content_block: Allow content block winners: calculate: Calculate Winner Investments calculated: Winners being calculated, it may take a minute. @@ -576,6 +580,8 @@ en: welcome: "Welcome" buttons: save: "Save" + content_block: + update: "Update Block" title_moderated_content: Moderated content title_budgets: Budgets title_polls: Polls diff --git a/config/locales/es/admin.yml b/config/locales/es/admin.yml index b16f85bd8..7fec85f34 100644 --- a/config/locales/es/admin.yml +++ b/config/locales/es/admin.yml @@ -1,4 +1,6 @@ es: + true_value: 'Si' + false_value: 'No' admin: header: title: Administración @@ -131,6 +133,7 @@ es: population_info: "El campo población de las partidas presupuestarias se usa con fines estadísticos únicamente, con el objetivo de mostrar el porcentaje de votos habidos en cada partida que represente un área con población. Es un campo opcional, así que puedes dejarlo en blanco si no aplica." max_votable_headings: "Máximo número de partidas en que un usuario puede votar" current_of_max_headings: "%{current} de %{max}" + allow_content_block: Permite bloque de contenidos winners: calculate: Calcular proyectos ganadores calculated: Calculando ganadores, puede tardar un minuto. @@ -575,6 +578,8 @@ es: welcome: "Bienvenido/a" buttons: save: "Guardar cambios" + content_block: + update: "Actualizar Bloque" title_moderated_content: Contenido moderado title_budgets: Presupuestos title_polls: Votaciones diff --git a/config/routes/admin.rb b/config/routes/admin.rb index e65ec688a..d8425ecf9 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -211,6 +211,9 @@ namespace :admin do resources :pages, except: [:show] resources :images, only: [:index, :update, :destroy] resources :content_blocks, except: [:show] + delete '/heading_content_blocks/:id', to: 'content_blocks#delete_heading_content_block', as: 'delete_heading_content_block' + get '/edit_heading_content_blocks/:id', to: 'content_blocks#edit_heading_content_block', as: 'edit_heading_content_block' + put '/update_heading_content_blocks/:id', to: 'content_blocks#update_heading_content_block', as: 'update_heading_content_block' resources :information_texts, only: [:index] do post :update, on: :collection end diff --git a/db/migrate/20181108103111_add_allow_custom_content_to_headings.rb b/db/migrate/20181108103111_add_allow_custom_content_to_headings.rb new file mode 100644 index 000000000..cf65b6fa3 --- /dev/null +++ b/db/migrate/20181108103111_add_allow_custom_content_to_headings.rb @@ -0,0 +1,5 @@ +class AddAllowCustomContentToHeadings < ActiveRecord::Migration + def change + add_column :budget_headings, :allow_custom_content, :boolean, default: false + end +end diff --git a/db/migrate/20181108142513_create_heading_content_blocks.rb b/db/migrate/20181108142513_create_heading_content_blocks.rb new file mode 100644 index 000000000..17db70e8c --- /dev/null +++ b/db/migrate/20181108142513_create_heading_content_blocks.rb @@ -0,0 +1,10 @@ +class CreateHeadingContentBlocks < ActiveRecord::Migration + def change + create_table :budget_content_blocks do |t| + t.integer :heading_id, index: true, foreign_key: true + t.text :body + t.string :locale + t.timestamps null: false + end + end +end diff --git a/db/schema.rb b/db/schema.rb index d30f35716..6339ba480 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20181016204729) do +ActiveRecord::Schema.define(version: 20181108142513) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -141,6 +141,16 @@ ActiveRecord::Schema.define(version: 20181016204729) do t.datetime "updated_at", null: false end + create_table "budget_content_blocks", force: :cascade do |t| + t.integer "heading_id" + t.text "body" + t.string "locale" + t.datetime "created_at", null: false + t.datetime "updated_at", null: false + end + + add_index "budget_content_blocks", ["heading_id"], name: "index_budget_content_blocks_on_heading_id", using: :btree + create_table "budget_groups", force: :cascade do |t| t.integer "budget_id" t.string "name", limit: 50 @@ -152,10 +162,11 @@ ActiveRecord::Schema.define(version: 20181016204729) do create_table "budget_headings", force: :cascade do |t| t.integer "group_id" - t.string "name", limit: 50 - t.integer "price", limit: 8 + t.string "name", limit: 50 + t.integer "price", limit: 8 t.integer "population" t.string "slug" + t.boolean "allow_custom_content", default: false end add_index "budget_headings", ["group_id"], name: "index_budget_headings_on_group_id", using: :btree diff --git a/spec/factories/budgets.rb b/spec/factories/budgets.rb index d713302fd..eaafde9a9 100644 --- a/spec/factories/budgets.rb +++ b/spec/factories/budgets.rb @@ -209,4 +209,10 @@ FactoryBot.define do factory :valuator_group, class: ValuatorGroup do sequence(:name) { |n| "Valuator Group #{n}" } end + + factory :heading_content_block, class: 'Budget::ContentBlock' do + association :heading, factory: :budget_heading + locale 'en' + body 'Some heading contents' + end end diff --git a/spec/features/admin/site_customization/content_blocks_spec.rb b/spec/features/admin/site_customization/content_blocks_spec.rb index 7bb15fdd0..f54189049 100644 --- a/spec/features/admin/site_customization/content_blocks_spec.rb +++ b/spec/features/admin/site_customization/content_blocks_spec.rb @@ -9,10 +9,13 @@ feature "Admin custom content blocks" do scenario "Index" do block = create(:site_customization_content_block) + heading_block = create(:heading_content_block) visit admin_site_customization_content_blocks_path expect(page).to have_content(block.name) expect(page).to have_content(block.body) + expect(page).to have_content(heading_block.heading.name) + expect(page).to have_content(heading_block.body) end context "Create" do diff --git a/spec/models/budget/heading_content_block_spec.rb b/spec/models/budget/heading_content_block_spec.rb new file mode 100644 index 000000000..7dd6c2f39 --- /dev/null +++ b/spec/models/budget/heading_content_block_spec.rb @@ -0,0 +1,20 @@ +require 'rails_helper' + +RSpec.describe Budget::ContentBlock do + let(:block) { build(:heading_content_block) } + + it "is valid" do + expect(block).to be_valid + end + + it "Heading is unique per locale" do + heading_content_block_en = create(:heading_content_block, locale: "en") + invalid_block = build(:heading_content_block, heading: heading_content_block_en.heading, locale: "en") + + expect(invalid_block).to be_invalid + expect(invalid_block.errors.full_messages).to include("Heading has already been taken") + + valid_block = build(:heading_content_block, heading: heading_content_block_en.heading, locale: "es") + expect(valid_block).to be_valid + end +end