Merge pull request #3296 from consul/make_budgets_translatable
[Backport] Make budgets translatable
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
class Admin::BudgetGroupsController < Admin::BaseController
|
||||
include Translatable
|
||||
include FeatureFlags
|
||||
feature_flag :budgets
|
||||
|
||||
@@ -57,7 +58,8 @@ class Admin::BudgetGroupsController < Admin::BaseController
|
||||
end
|
||||
|
||||
def budget_group_params
|
||||
params.require(:budget_group).permit(:name, :max_votable_headings)
|
||||
valid_attributes = [:max_votable_headings]
|
||||
params.require(:budget_group).permit(*valid_attributes, translation_params(Budget::Group))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
class Admin::BudgetHeadingsController < Admin::BaseController
|
||||
include Translatable
|
||||
include FeatureFlags
|
||||
feature_flag :budgets
|
||||
|
||||
@@ -62,8 +63,8 @@ class Admin::BudgetHeadingsController < Admin::BaseController
|
||||
end
|
||||
|
||||
def budget_heading_params
|
||||
params.require(:budget_heading).permit(:name, :price, :population, :allow_custom_content,
|
||||
:latitude, :longitude)
|
||||
valid_attributes = [:price, :population, :allow_custom_content, :latitude, :longitude]
|
||||
params.require(:budget_heading).permit(*valid_attributes, translation_params(Budget::Heading))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
class Admin::BudgetPhasesController < Admin::BaseController
|
||||
include Translatable
|
||||
|
||||
before_action :load_phase, only: [:edit, :update]
|
||||
|
||||
@@ -21,8 +22,8 @@ class Admin::BudgetPhasesController < Admin::BaseController
|
||||
end
|
||||
|
||||
def budget_phase_params
|
||||
valid_attributes = [:starts_at, :ends_at, :summary, :description, :enabled]
|
||||
params.require(:budget_phase).permit(*valid_attributes)
|
||||
valid_attributes = [:starts_at, :ends_at, :enabled]
|
||||
params.require(:budget_phase).permit(*valid_attributes, translation_params(Budget::Phase))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
class Admin::BudgetsController < Admin::BaseController
|
||||
include Translatable
|
||||
include FeatureFlags
|
||||
feature_flag :budgets
|
||||
|
||||
@@ -56,8 +57,8 @@ class Admin::BudgetsController < Admin::BaseController
|
||||
|
||||
def budget_params
|
||||
descriptions = Budget::Phase::PHASE_KINDS.map{|p| "description_#{p}"}.map(&:to_sym)
|
||||
valid_attributes = [:name, :phase, :currency_symbol] + descriptions
|
||||
params.require(:budget).permit(*valid_attributes)
|
||||
valid_attributes = [:phase, :currency_symbol] + descriptions
|
||||
params.require(:budget).permit(*valid_attributes, translation_params(Budget))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -75,8 +75,8 @@ class Valuation::BudgetInvestmentsController < Valuation::BaseController
|
||||
def heading_filters
|
||||
investments = @budget.investments.by_valuator(current_user.valuator.try(:id))
|
||||
.visible_to_valuators.distinct
|
||||
|
||||
investment_headings = Budget::Heading.where(id: investments.pluck(:heading_id).uniq)
|
||||
investment_headings = Budget::Heading.joins(:translations)
|
||||
.where(id: investments.pluck(:heading_id).uniq)
|
||||
.order(name: :asc)
|
||||
|
||||
all_headings_filter = [
|
||||
|
||||
@@ -30,7 +30,7 @@ module AdminHelper
|
||||
end
|
||||
|
||||
def menu_budgets?
|
||||
%w[budgets budget_groups budget_headings budget_investments].include?(controller_name)
|
||||
controller_name.starts_with?("budget")
|
||||
end
|
||||
|
||||
def menu_budget?
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
module BudgetHeadingsHelper
|
||||
|
||||
def budget_heading_select_options(budget)
|
||||
budget.headings.order_by_group_name.map do |heading|
|
||||
budget.headings.sort_by_name.map do |heading|
|
||||
[heading.name_scoped_by_group, heading.id]
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,9 +3,14 @@ class Budget < ActiveRecord::Base
|
||||
include Measurable
|
||||
include Sluggable
|
||||
|
||||
translates :name, touch: true
|
||||
include Globalizable
|
||||
|
||||
CURRENCY_SYMBOLS = %w(€ $ £ ¥).freeze
|
||||
|
||||
validates :name, presence: true, uniqueness: true
|
||||
before_validation :assign_model_to_translations
|
||||
|
||||
validates_translation :name, presence: true
|
||||
validates :phase, inclusion: { in: Budget::Phase::PHASE_KINDS }
|
||||
validates :currency_symbol, presence: true
|
||||
validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/
|
||||
|
||||
@@ -2,14 +2,21 @@ class Budget
|
||||
class Group < ActiveRecord::Base
|
||||
include Sluggable
|
||||
|
||||
translates :name, touch: true
|
||||
include Globalizable
|
||||
|
||||
belongs_to :budget
|
||||
|
||||
has_many :headings, dependent: :destroy
|
||||
|
||||
before_validation :assign_model_to_translations
|
||||
|
||||
validates_translation :name, presence: true
|
||||
validates :budget_id, presence: true
|
||||
validates :name, presence: true, uniqueness: { scope: :budget }
|
||||
validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/
|
||||
|
||||
scope :sort_by_name, -> { includes(:translations).order(:name) }
|
||||
|
||||
def single_heading_group?
|
||||
headings.count == 1
|
||||
end
|
||||
|
||||
13
app/models/budget/group/translation.rb
Normal file
13
app/models/budget/group/translation.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
class Budget::Group::Translation < Globalize::ActiveRecord::Translation
|
||||
delegate :budget, to: :globalized_model
|
||||
|
||||
validate :name_uniqueness_by_budget
|
||||
|
||||
def name_uniqueness_by_budget
|
||||
if budget.groups.joins(:translations)
|
||||
.where(name: name)
|
||||
.where.not("budget_group_translations.budget_group_id": budget_group_id).any?
|
||||
errors.add(:name, I18n.t("errors.messages.taken"))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -4,13 +4,18 @@ class Budget
|
||||
|
||||
include Sluggable
|
||||
|
||||
translates :name, touch: true
|
||||
include Globalizable
|
||||
|
||||
belongs_to :group
|
||||
|
||||
has_many :investments
|
||||
has_many :content_blocks
|
||||
|
||||
before_validation :assign_model_to_translations
|
||||
|
||||
validates_translation :name, presence: true
|
||||
validates :group_id, presence: true
|
||||
validates :name, presence: true, uniqueness: { if: :name_exists_in_budget_headings }
|
||||
validates :price, presence: true
|
||||
validates :slug, presence: true, format: /\A[a-z0-9\-_]+\z/
|
||||
validates :population, numericality: { greater_than: 0 }, allow_nil: true
|
||||
@@ -21,17 +26,19 @@ 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) }
|
||||
scope :i18n, -> { includes(:translations) }
|
||||
scope :allow_custom_content, -> { i18n.where(allow_custom_content: true).order(:name) }
|
||||
|
||||
def self.sort_by_name
|
||||
all.sort do |heading, other_heading|
|
||||
[other_heading.group.name, heading.name] <=> [heading.group.name, other_heading.name]
|
||||
end
|
||||
end
|
||||
|
||||
def name_scoped_by_group
|
||||
group.single_heading_group? ? name : "#{group.name}: #{name}"
|
||||
end
|
||||
|
||||
def name_exists_in_budget_headings
|
||||
group.budget.headings.where(name: name).where.not(id: id).any?
|
||||
end
|
||||
|
||||
def can_be_deleted?
|
||||
investments.empty?
|
||||
end
|
||||
|
||||
14
app/models/budget/heading/translation.rb
Normal file
14
app/models/budget/heading/translation.rb
Normal file
@@ -0,0 +1,14 @@
|
||||
class Budget::Heading::Translation < Globalize::ActiveRecord::Translation
|
||||
delegate :budget, to: :globalized_model
|
||||
|
||||
validate :name_uniqueness_by_budget
|
||||
|
||||
def name_uniqueness_by_budget
|
||||
if budget.headings
|
||||
.joins(:translations)
|
||||
.where(name: name)
|
||||
.where.not("budget_heading_translations.budget_heading_id": budget_heading_id).any?
|
||||
errors.add(:name, I18n.t("errors.messages.taken"))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,20 +6,22 @@ class Budget
|
||||
SUMMARY_MAX_LENGTH = 1000
|
||||
DESCRIPTION_MAX_LENGTH = 2000
|
||||
|
||||
translates :summary, touch: true
|
||||
translates :description, touch: true
|
||||
include Globalizable
|
||||
|
||||
belongs_to :budget
|
||||
belongs_to :next_phase, class_name: 'Budget::Phase', foreign_key: :next_phase_id
|
||||
has_one :prev_phase, class_name: 'Budget::Phase', foreign_key: :next_phase_id
|
||||
|
||||
validates_translation :summary, length: { maximum: SUMMARY_MAX_LENGTH }
|
||||
validates_translation :description, length: { maximum: DESCRIPTION_MAX_LENGTH }
|
||||
validates :budget, presence: true
|
||||
validates :kind, presence: true, uniqueness: { scope: :budget }, inclusion: { in: PHASE_KINDS }
|
||||
validates :summary, length: { maximum: SUMMARY_MAX_LENGTH }
|
||||
validates :description, length: { maximum: DESCRIPTION_MAX_LENGTH }
|
||||
validate :invalid_dates_range?
|
||||
validate :prev_phase_dates_valid?
|
||||
validate :next_phase_dates_valid?
|
||||
|
||||
before_validation :sanitize_description
|
||||
|
||||
after_save :adjust_date_ranges
|
||||
after_save :touch_budget
|
||||
|
||||
@@ -87,8 +89,5 @@ class Budget
|
||||
end
|
||||
end
|
||||
|
||||
def sanitize_description
|
||||
self.description = WYSIWYGSanitizer.new.sanitize(description)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
9
app/models/budget/phase/translation.rb
Normal file
9
app/models/budget/phase/translation.rb
Normal file
@@ -0,0 +1,9 @@
|
||||
class Budget::Phase::Translation < Globalize::ActiveRecord::Translation
|
||||
before_validation :sanitize_description
|
||||
|
||||
private
|
||||
|
||||
def sanitize_description
|
||||
self.description = WYSIWYGSanitizer.new.sanitize(description)
|
||||
end
|
||||
end
|
||||
11
app/models/budget/translation.rb
Normal file
11
app/models/budget/translation.rb
Normal file
@@ -0,0 +1,11 @@
|
||||
class Budget::Translation < Globalize::ActiveRecord::Translation
|
||||
validate :name_uniqueness_by_budget
|
||||
|
||||
def name_uniqueness_by_budget
|
||||
if Budget.joins(:translations)
|
||||
.where(name: name)
|
||||
.where.not("budget_translations.budget_id": budget_id).any?
|
||||
errors.add(:name, I18n.t("errors.messages.taken"))
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8,6 +8,10 @@ module Globalizable
|
||||
def locales_not_marked_for_destruction
|
||||
translations.reject(&:_destroy).map(&:locale)
|
||||
end
|
||||
|
||||
def assign_model_to_translations
|
||||
translations.each { |translation| translation.globalized_model = self }
|
||||
end
|
||||
end
|
||||
|
||||
class_methods do
|
||||
|
||||
@@ -14,7 +14,7 @@ class Milestone < ActiveRecord::Base
|
||||
validates :milestoneable, presence: true
|
||||
validates :publication_date, presence: true
|
||||
|
||||
before_validation :assign_milestone_to_translations
|
||||
before_validation :assign_model_to_translations
|
||||
validates_translation :description, presence: true, unless: -> { status_id.present? }
|
||||
|
||||
scope :order_by_publication_date, -> { order(publication_date: :asc, created_at: :asc) }
|
||||
@@ -25,9 +25,4 @@ class Milestone < ActiveRecord::Base
|
||||
80
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assign_milestone_to_translations
|
||||
translations.each { |translation| translation.globalized_model = self }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -17,12 +17,7 @@ class ProgressBar < ActiveRecord::Base
|
||||
}
|
||||
validates :percentage, presence: true, inclusion: RANGE, numericality: { only_integer: true }
|
||||
|
||||
before_validation :assign_progress_bar_to_translations
|
||||
before_validation :assign_model_to_translations
|
||||
validates_translation :title, presence: true, unless: :primary?
|
||||
|
||||
private
|
||||
|
||||
def assign_progress_bar_to_translations
|
||||
translations.each { |translation| translation.globalized_model = self }
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,10 +1,16 @@
|
||||
<div class="small-12 medium-6">
|
||||
<%= form_for [:admin, @budget, @group], url: path do |f| %>
|
||||
<%= render "admin/shared/globalize_locales", resource: @group %>
|
||||
|
||||
<%= f.text_field :name,
|
||||
label: t("admin.budget_groups.form.name"),
|
||||
maxlength: 50,
|
||||
placeholder: t("admin.budget_groups.form.name") %>
|
||||
<div class="small-12 medium-6">
|
||||
<%= translatable_form_for [:admin, @budget, @group], url: path do |f| %>
|
||||
|
||||
<%= render 'shared/errors', resource: @group %>
|
||||
|
||||
<%= f.translatable_fields do |translations_form| %>
|
||||
<%= translations_form.text_field :name,
|
||||
label: t("admin.budget_groups.form.name"),
|
||||
maxlength: 50,
|
||||
placeholder: t("admin.budget_groups.form.name") %>
|
||||
<% end %>
|
||||
|
||||
<% if @group.persisted? %>
|
||||
<%= f.select :max_votable_headings,
|
||||
|
||||
@@ -1,11 +1,17 @@
|
||||
<%= render "admin/shared/globalize_locales", resource: @heading %>
|
||||
|
||||
<div class="small-12 medium-6">
|
||||
|
||||
<%= form_for [:admin, @budget, @group, @heading], url: path do |f| %>
|
||||
<%= translatable_form_for [:admin, @budget, @group, @heading], url: path do |f| %>
|
||||
|
||||
<%= f.text_field :name,
|
||||
label: t("admin.budget_headings.form.name"),
|
||||
maxlength: 50,
|
||||
placeholder: t("admin.budget_headings.form.name") %>
|
||||
<%= render 'shared/errors', resource: @heading %>
|
||||
|
||||
<%= f.translatable_fields do |translations_form| %>
|
||||
<%= translations_form.text_field :name,
|
||||
label: t("admin.budget_headings.form.name"),
|
||||
maxlength: 50,
|
||||
placeholder: t("admin.budget_headings.form.name") %>
|
||||
<% end %>
|
||||
|
||||
<%= f.text_field :price,
|
||||
label: t("admin.budget_headings.form.amount"),
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
<%= form_for [:admin, @phase.budget, @phase] do |f| %>
|
||||
<%= render "admin/shared/globalize_locales", resource: @phase %>
|
||||
|
||||
<%= translatable_form_for [:admin, @phase.budget, @phase] do |f| %>
|
||||
|
||||
<%= render 'shared/errors', resource: @phase %>
|
||||
|
||||
<div class="small-12 medium-6 column">
|
||||
<%= f.label :starts_at, t("admin.budget_phases.edit.start_date") %>
|
||||
@@ -18,32 +22,34 @@
|
||||
</div>
|
||||
|
||||
<div class="small-12 column">
|
||||
<%= f.label :description, t("admin.budget_phases.edit.description") %>
|
||||
<%= f.translatable_fields do |translations_form| %>
|
||||
|
||||
<span class="help-text" id="phase-description-help-text">
|
||||
<%= t("admin.budget_phases.edit.description_help_text") %>
|
||||
</span>
|
||||
<%= f.label :description, t("admin.budget_phases.edit.description") %>
|
||||
|
||||
<%= f.cktext_area :description,
|
||||
maxlength: Budget::Phase::DESCRIPTION_MAX_LENGTH,
|
||||
ckeditor: { language: I18n.locale },
|
||||
label: false %>
|
||||
<span class="help-text" id="phase-description-help-text">
|
||||
<%= t("admin.budget_phases.edit.description_help_text") %>
|
||||
</span>
|
||||
|
||||
<div class="ckeditor">
|
||||
<%= translations_form.cktext_area :description,
|
||||
maxlength: Budget::Phase::DESCRIPTION_MAX_LENGTH,
|
||||
label: false %>
|
||||
</div>
|
||||
|
||||
<%= f.label :summary, t("admin.budget_phases.edit.summary") %>
|
||||
|
||||
<span class="help-text" id="phase-summary-help-text">
|
||||
<%= t("admin.budget_phases.edit.summary_help_text") %>
|
||||
</span>
|
||||
|
||||
<div class="ckeditor">
|
||||
<%= translations_form.cktext_area :summary,
|
||||
maxlength: Budget::Phase::SUMMARY_MAX_LENGTH,
|
||||
label: false%>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="small-12 column margin-top">
|
||||
<%= f.label :summary, t("admin.budget_phases.edit.summary") %>
|
||||
|
||||
<span class="help-text" id="phase-summary-help-text">
|
||||
<%= t("admin.budget_phases.edit.summary_help_text") %>
|
||||
</span>
|
||||
|
||||
<%= f.cktext_area :summary,
|
||||
maxlength: Budget::Phase::SUMMARY_MAX_LENGTH,
|
||||
ckeditor: { language: I18n.locale },
|
||||
label: false %>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="small-12 column margin-top">
|
||||
<%= f.check_box :enabled, label: t("admin.budget_phases.edit.enabled") %>
|
||||
|
||||
|
||||
@@ -1,7 +1,16 @@
|
||||
<%= form_for [:admin, @budget] do |f| %>
|
||||
<%= render "admin/shared/globalize_locales", resource: @budget %>
|
||||
|
||||
<%= translatable_form_for [:admin, @budget] do |f| %>
|
||||
|
||||
<%= render 'shared/errors', resource: @budget %>
|
||||
|
||||
<div class="small-12 medium-9 column">
|
||||
<%= f.text_field :name, maxlength: Budget.title_max_length %>
|
||||
<%= f.translatable_fields do |translations_form| %>
|
||||
<%= translations_form.text_field :name,
|
||||
label: t("activerecord.attributes.budget.name"),
|
||||
maxlength: Budget.title_max_length,
|
||||
placeholder: t("activerecord.attributes.budget.name") %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div class="margin-top">
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
</div>
|
||||
|
||||
<div class="row ballot">
|
||||
<% ballot_groups = @ballot.groups.order(name: :asc) %>
|
||||
<% ballot_groups = @ballot.groups.sort_by_name %>
|
||||
<% ballot_groups.each do |group| %>
|
||||
<div id="<%= dom_id(group) %>" class="small-12 medium-6 column end">
|
||||
<div class="margin-top ballot-content">
|
||||
@@ -52,7 +52,7 @@
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% no_balloted_groups = @budget.groups.order(name: :asc) - ballot_groups %>
|
||||
<% no_balloted_groups = @budget.groups.sort_by_name - ballot_groups %>
|
||||
<% no_balloted_groups.each do |group| %>
|
||||
<div id="<%= dom_id(group) %>" class="small-12 medium-6 column end">
|
||||
<div class="margin-top ballot-content">
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<div class="row margin">
|
||||
<div id="headings" class="small-12 medium-7 column select-district">
|
||||
<div class="row">
|
||||
<% @group.headings.order_by_group_name.each_slice(7) do |slice| %>
|
||||
<% @group.headings.sort_by_name.each_slice(7) do |slice| %>
|
||||
<div class="small-6 medium-4 column end">
|
||||
<% slice.each do |heading| %>
|
||||
<span id="<%= dom_id(heading) %>"
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<% current_budget.groups.each do |group| %>
|
||||
<h2 id="<%= group.name.parameterize %>"><%= group.name %></h2>
|
||||
<ul class="no-bullet" data-equalizer data-equalizer-on="medium">
|
||||
<% group.headings.order_by_group_name.each do |heading| %>
|
||||
<% group.headings.sort_by_name.each do |heading| %>
|
||||
<li class="heading small-12 medium-4 large-2" data-equalizer-watch>
|
||||
<% unless current_budget.informing? || current_budget.finished? %>
|
||||
<%= link_to budget_investments_path(current_budget.id,
|
||||
|
||||
Reference in New Issue
Block a user