Merge pull request #3296 from consul/make_budgets_translatable

[Backport] Make budgets translatable
This commit is contained in:
Julian Nicolas Herrero
2019-02-13 19:01:13 +01:00
committed by GitHub
46 changed files with 668 additions and 152 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = [

View File

@@ -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?

View File

@@ -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

View File

@@ -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/

View File

@@ -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

View 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

View File

@@ -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

View 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

View File

@@ -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

View 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

View 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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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,

View File

@@ -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"),

View File

@@ -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") %>

View File

@@ -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">

View File

@@ -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">

View File

@@ -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) %>"

View File

@@ -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,