aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--app/assets/javascripts/admin.js3
-rw-r--r--app/assets/javascripts/admin/category-order.js42
-rw-r--r--app/assets/stylesheets/admin.scss15
-rw-r--r--app/assets/stylesheets/responsive/_user_layout.scss4
-rw-r--r--app/controllers/admin_public_body_categories_controller.rb84
-rw-r--r--app/controllers/admin_public_body_controller.rb2
-rw-r--r--app/controllers/admin_public_body_headings_controller.rb113
-rw-r--r--app/controllers/admin_spam_addresses_controller.rb4
-rw-r--r--app/controllers/public_body_controller.rb4
-rw-r--r--app/controllers/user_controller.rb15
-rw-r--r--app/helpers/admin_public_body_category_helper.rb14
-rw-r--r--app/models/public_body.rb14
-rw-r--r--app/models/public_body_category.rb90
-rw-r--r--app/models/public_body_category/category_collection.rb54
-rw-r--r--app/models/public_body_category_link.rb34
-rw-r--r--app/models/public_body_heading.rb75
-rw-r--r--app/views/admin_general/_admin_navbar.html.erb1
-rw-r--r--app/views/admin_public_body/_tag_help.html.erb2
-rw-r--r--app/views/admin_public_body/import_csv.html.erb2
-rw-r--r--app/views/admin_public_body_categories/_category_list_item.html.erb5
-rw-r--r--app/views/admin_public_body_categories/_form.html.erb66
-rw-r--r--app/views/admin_public_body_categories/_heading_list.html.erb26
-rw-r--r--app/views/admin_public_body_categories/edit.html.erb30
-rw-r--r--app/views/admin_public_body_categories/index.html.erb28
-rw-r--r--app/views/admin_public_body_categories/new.html.erb21
-rw-r--r--app/views/admin_public_body_headings/_form.html.erb41
-rw-r--r--app/views/admin_public_body_headings/edit.html.erb30
-rw-r--r--app/views/admin_public_body_headings/new.html.erb21
-rw-r--r--app/views/admin_request/_incoming_message_actions.html.erb2
-rw-r--r--app/views/admin_spam_addresses/index.html.erb4
-rw-r--r--app/views/public_body/list.html.erb2
-rw-r--r--app/views/user/show.html.erb3
-rw-r--r--config/initializers/alaveteli.rb4
-rw-r--r--config/routes.rb20
-rw-r--r--db/migrate/20140710094405_create_public_body_headings_and_categories.rb27
-rw-r--r--db/migrate/20140716131107_create_category_translation_tables.rb163
-rw-r--r--db/migrate/20140804120601_add_display_order_to_categories_and_headings.rb15
-rw-r--r--doc/CHANGES.md6
-rw-r--r--lib/category_and_heading_migrator.rb91
-rw-r--r--lib/public_body_categories.rb61
-rw-r--r--lib/public_body_categories_en.rb19
-rwxr-xr-xscript/migrate-public-body-categories4
-rw-r--r--spec/controllers/admin_public_body_categories_controller_spec.rb192
-rw-r--r--spec/controllers/admin_public_body_headings_controller_spec.rb240
-rw-r--r--spec/controllers/admin_spam_addresses_controller_spec.rb4
-rw-r--r--spec/controllers/public_body_controller_spec.rb22
-rw-r--r--spec/controllers/user_controller_spec.rb12
-rw-r--r--spec/factories/public_body_categories.rb8
-rw-r--r--spec/factories/public_body_category_links.rb6
-rw-r--r--spec/factories/public_body_headings.rb5
-rw-r--r--spec/fixtures/info_request_events.yml4
-rw-r--r--spec/lib/public_body_categories_spec.rb42
-rw-r--r--spec/models/public_body_category/category_collection_spec.rb81
-rw-r--r--spec/models/public_body_category_link_spec.rb53
-rw-r--r--spec/models/public_body_category_spec.rb65
-rw-r--r--spec/models/public_body_heading_spec.rb68
-rw-r--r--spec/spec_helper.rb8
57 files changed, 1913 insertions, 158 deletions
diff --git a/app/assets/javascripts/admin.js b/app/assets/javascripts/admin.js
index 0b5d56525..4925a65a4 100644
--- a/app/assets/javascripts/admin.js
+++ b/app/assets/javascripts/admin.js
@@ -1,7 +1,10 @@
// ...
//= require jquery
//= require jquery.ui.tabs
+//= require jquery.ui.sortable
+//= require jquery.ui.effect-highlight
//= require admin/bootstrap-collapse
//= require admin/bootstrap-tab
//= require admin/admin
+//= require admin/category-order
//= require jquery_ujs
diff --git a/app/assets/javascripts/admin/category-order.js b/app/assets/javascripts/admin/category-order.js
new file mode 100644
index 000000000..3be82e46f
--- /dev/null
+++ b/app/assets/javascripts/admin/category-order.js
@@ -0,0 +1,42 @@
+$(function() {
+ $('.save-order').each(function(index){
+
+ // identify the elements that will work together
+ var save_button = $(this);
+ var save_notice = save_button.next();
+ var save_panel = save_button.parent();
+ var list_element = $(save_button.data('list-id'));
+ var endpoint = save_button.data('endpoint');
+
+ // on the first list change, show that there are unsaved changes
+ list_element.sortable({
+ update: function (event, ui) {
+ if (save_button.is('.disabled')){
+ save_button.removeClass("disabled");
+ save_notice.html(save_notice.data('unsaved-text'));
+ save_panel.effect('highlight', {}, 2000);
+ }
+ }
+ });
+ // on save, POST to endpoint
+ save_button.click(function(){
+ if (!save_button.is('.disabled')){
+ var data = list_element.sortable('serialize', {'attribute': 'data-id'});
+ var update_call = $.ajax({ data: data, type: 'POST', url: endpoint });
+
+ // on success, disable the save button again, and show success notice
+ update_call.done(function(msg) {
+ save_button.addClass('disabled');
+ save_panel.effect('highlight', {}, 2000);
+ save_notice.html(save_notice.data('success-text'));
+ })
+ // on failure, show error message
+ update_call.fail(function(jqXHR, msg) {
+ save_panel.effect('highlight', {'color': '#cc0000'}, 2000);
+ save_notice.html(save_notice.data('error-text') + jqXHR.responseText);
+ });
+ }
+ return false;
+ })
+ });
+});
diff --git a/app/assets/stylesheets/admin.scss b/app/assets/stylesheets/admin.scss
index b0de2eb7b..863a6c808 100644
--- a/app/assets/stylesheets/admin.scss
+++ b/app/assets/stylesheets/admin.scss
@@ -27,6 +27,9 @@ body.admin {
}
.admin {
+ font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 14px;
+ line-height: 20px;
@import "compass/css3";
@import "bootstrap";
@@ -47,6 +50,9 @@ body.admin {
.accordion-group {
border: none;
+ div {
+ clear: both;
+ }
}
.accordion-heading {
.btn {
@@ -104,5 +110,14 @@ body.admin {
width: 750px;
}
+ .save-notice {
+ display: inline-block;
+ padding-left: 1em;
+ }
+
+ .category-list-item {
+ padding: 3px 0;
+ }
+
}
diff --git a/app/assets/stylesheets/responsive/_user_layout.scss b/app/assets/stylesheets/responsive/_user_layout.scss
index a568a5fa3..84ddbf562 100644
--- a/app/assets/stylesheets/responsive/_user_layout.scss
+++ b/app/assets/stylesheets/responsive/_user_layout.scss
@@ -4,4 +4,8 @@
#search_form {
margin-top: 2rem;
}
+
+ #request_latest_status {
+ width: 300px;
+ }
}
diff --git a/app/controllers/admin_public_body_categories_controller.rb b/app/controllers/admin_public_body_categories_controller.rb
new file mode 100644
index 000000000..fda09fa4a
--- /dev/null
+++ b/app/controllers/admin_public_body_categories_controller.rb
@@ -0,0 +1,84 @@
+class AdminPublicBodyCategoriesController < AdminController
+ def index
+ @locale = self.locale_from_params
+ @category_headings = PublicBodyHeading.all
+ @without_heading = PublicBodyCategory.without_headings
+ end
+
+ def new
+ @category = PublicBodyCategory.new
+ render :formats => [:html]
+ end
+
+ def edit
+ @category = PublicBodyCategory.find(params[:id])
+ @tagged_public_bodies = PublicBody.find_by_tag(@category.category_tag)
+ end
+
+ def update
+ @category = PublicBodyCategory.find(params[:id])
+ @tagged_public_bodies = PublicBody.find_by_tag(@category.category_tag)
+ heading_ids = []
+
+ I18n.with_locale(I18n.default_locale) do
+ if params[:public_body_category][:category_tag] && PublicBody.find_by_tag(@category.category_tag).count > 0 && @category.category_tag != params[:public_body_category][:category_tag]
+ flash[:notice] = 'There are authorities associated with this category, so the tag can\'t be renamed'
+ else
+ if params[:headings]
+ heading_ids = params[:headings].values
+ removed_headings = @category.public_body_heading_ids - heading_ids
+ added_headings = heading_ids - @category.public_body_heading_ids
+
+ unless removed_headings.empty?
+ # remove the link objects
+ deleted_links = PublicBodyCategoryLink.where(
+ :public_body_category_id => @category.id,
+ :public_body_heading_id => [removed_headings]
+ )
+ deleted_links.delete_all
+
+ #fix the category object
+ @category.public_body_heading_ids = heading_ids
+ end
+
+ added_headings.each do |heading_id|
+ PublicBodyHeading.find(heading_id).add_category(@category)
+ end
+ end
+
+ if @category.update_attributes(params[:public_body_category])
+ flash[:notice] = 'Category was successfully updated.'
+ end
+ end
+
+ render :action => 'edit'
+ end
+ end
+
+ def create
+ I18n.with_locale(I18n.default_locale) do
+ @category = PublicBodyCategory.new(params[:public_body_category])
+ if @category.save
+ if params[:headings]
+ params[:headings].values.each do |heading_id|
+ PublicBodyHeading.find(heading_id).add_category(@category)
+ end
+ end
+ flash[:notice] = 'Category was successfully created.'
+ redirect_to admin_categories_path
+ else
+ render :action => 'new'
+ end
+ end
+ end
+
+ def destroy
+ @locale = self.locale_from_params
+ I18n.with_locale(@locale) do
+ category = PublicBodyCategory.find(params[:id])
+ category.destroy
+ flash[:notice] = "Category was successfully destroyed."
+ redirect_to admin_categories_path
+ end
+ end
+end
diff --git a/app/controllers/admin_public_body_controller.rb b/app/controllers/admin_public_body_controller.rb
index 120419a27..f7a80476c 100644
--- a/app/controllers/admin_public_body_controller.rb
+++ b/app/controllers/admin_public_body_controller.rb
@@ -4,8 +4,6 @@
# Copyright (c) 2007 UK Citizens Online Democracy. All rights reserved.
# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
-require "public_body_categories"
-
class AdminPublicBodyController < AdminController
def index
list
diff --git a/app/controllers/admin_public_body_headings_controller.rb b/app/controllers/admin_public_body_headings_controller.rb
new file mode 100644
index 000000000..c7c80e802
--- /dev/null
+++ b/app/controllers/admin_public_body_headings_controller.rb
@@ -0,0 +1,113 @@
+class AdminPublicBodyHeadingsController < AdminController
+
+ def edit
+ @heading = PublicBodyHeading.find(params[:id])
+ render :formats => [:html]
+ end
+
+ def update
+ I18n.with_locale(I18n.default_locale) do
+ @heading = PublicBodyHeading.find(params[:id])
+ if @heading.update_attributes(params[:public_body_heading])
+ flash[:notice] = 'Category heading was successfully updated.'
+ end
+ render :action => 'edit'
+ end
+ end
+
+ def reorder
+ transaction = reorder_headings(params[:headings])
+ if transaction[:success]
+ render :nothing => true, :status => :ok
+ else
+ render :text => transaction[:error], :status => :unprocessable_entity
+ end
+ end
+
+ def reorder_categories
+ transaction = reorder_categories_for_heading(params[:id], params[:categories])
+ if transaction[:success]
+ render :nothing => true, :status => :ok and return
+ else
+ render :text => transaction[:error], :status => :unprocessable_entity
+ end
+ end
+
+ def new
+ @heading = PublicBodyHeading.new
+ render :formats => [:html]
+ end
+
+ def create
+ I18n.with_locale(I18n.default_locale) do
+ @heading = PublicBodyHeading.new(params[:public_body_heading])
+ if @heading.save
+ flash[:notice] = 'Category heading was successfully created.'
+ redirect_to admin_categories_url
+ else
+ render :action => 'new'
+ end
+ end
+ end
+
+ def destroy
+ @locale = self.locale_from_params()
+ I18n.with_locale(@locale) do
+ heading = PublicBodyHeading.find(params[:id])
+
+ if heading.public_body_categories.count > 0
+ flash[:notice] = "There are categories associated with this heading, so can't destroy it"
+ redirect_to edit_admin_heading_url(heading)
+ return
+ end
+
+ heading.destroy
+ flash[:notice] = "Category heading was successfully destroyed."
+ redirect_to admin_categories_url
+ end
+ end
+
+ protected
+
+ def reorder_headings(headings)
+ error = nil
+ ActiveRecord::Base.transaction do
+ headings.each_with_index do |heading_id, index|
+ begin
+ heading = PublicBodyHeading.find(heading_id)
+ rescue ActiveRecord::RecordNotFound => e
+ error = e.message
+ raise ActiveRecord::Rollback
+ end
+ heading.display_order = index
+ unless heading.save
+ error = heading.errors.full_messages.join(",")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+ { :success => error.nil? ? true : false, :error => error }
+ end
+
+ def reorder_categories_for_heading(heading_id, categories)
+ error = nil
+ ActiveRecord::Base.transaction do
+ categories.each_with_index do |category_id, index|
+ conditions = { :public_body_category_id => category_id,
+ :public_body_heading_id => heading_id }
+ link = PublicBodyCategoryLink.where(conditions).first
+ unless link
+ error = "Couldn't find PublicBodyCategoryLink for category #{category_id}, heading #{heading_id}"
+ raise ActiveRecord::Rollback
+ end
+ link.category_display_order = index
+ unless link.save
+ error = link.errors.full_messages.join(",")
+ raise ActiveRecord::Rollback
+ end
+ end
+ end
+ { :success => error.nil? ? true : false, :error => error }
+ end
+
+end
diff --git a/app/controllers/admin_spam_addresses_controller.rb b/app/controllers/admin_spam_addresses_controller.rb
index f5c7e93da..fff7e2a4a 100644
--- a/app/controllers/admin_spam_addresses_controller.rb
+++ b/app/controllers/admin_spam_addresses_controller.rb
@@ -10,7 +10,7 @@ class AdminSpamAddressesController < AdminController
if @spam_address.save
notice = "#{ @spam_address.email } has been added to the spam addresses list"
- redirect_to spam_addresses_path, :notice => notice
+ redirect_to admin_spam_addresses_path, :notice => notice
else
@spam_addresses = SpamAddress.all
render :index
@@ -21,7 +21,7 @@ class AdminSpamAddressesController < AdminController
@spam_address = SpamAddress.find(params[:id])
@spam_address.destroy
notice = "#{ @spam_address.email } has been removed from the spam addresses list"
- redirect_to spam_addresses_path, :notice => notice
+ redirect_to admin_spam_addresses_path, :notice => notice
end
end
diff --git a/app/controllers/public_body_controller.rb b/app/controllers/public_body_controller.rb
index d2c84d820..e64644a1b 100644
--- a/app/controllers/public_body_controller.rb
+++ b/app/controllers/public_body_controller.rb
@@ -111,7 +111,7 @@ class PublicBodyController < ApplicationController
if @tag.nil? || @tag == 'all'
@tag = 'all'
elsif @tag == 'other'
- category_list = PublicBodyCategories.get.tags.map{ |c| %Q('#{ c }') }.join(",")
+ category_list = PublicBodyCategory.get.tags.map{ |c| %Q('#{ c }') }.join(",")
where_condition += base_tag_condition + " AND has_tag_string_tags.name in (#{category_list})) = 0"
elsif @tag.scan(/./mu).size == 1
@tag = Unicode.upcase(@tag)
@@ -132,7 +132,7 @@ class PublicBodyController < ApplicationController
elsif @tag.size == 1
@description = _("beginning with ‘{{first_letter}}’", :first_letter => @tag)
else
- category_name = PublicBodyCategories.get.by_tag[@tag]
+ category_name = PublicBodyCategory.get.by_tag[@tag]
if category_name.nil?
@description = _("matching the tag ‘{{tag_name}}’", :tag_name => @tag)
else
diff --git a/app/controllers/user_controller.rb b/app/controllers/user_controller.rb
index f23343ddb..baeaab18a 100644
--- a/app/controllers/user_controller.rb
+++ b/app/controllers/user_controller.rb
@@ -49,13 +49,28 @@ class UserController < ApplicationController
# TODO: really should just use SQL query here rather than Xapian.
if @show_requests
begin
+
+ request_states = @display_user.info_requests.pluck(:described_state).uniq
+
+ option_item = Struct.new(:value, :text)
+ @request_states = request_states.map do |state|
+ option_item.new(state, InfoRequest.get_status_description(state))
+ end
+
requests_query = 'requested_by:' + @display_user.url_name
comments_query = 'commented_by:' + @display_user.url_name
if !params[:user_query].nil?
requests_query += " " + params[:user_query]
comments_query += " " + params[:user_query]
@match_phrase = _("{{search_results}} matching '{{query}}'", :search_results => "", :query => params[:user_query])
+
+ unless params[:request_latest_status].blank?
+ requests_query << ' latest_status:' << params[:request_latest_status]
+ comments_query << ' latest_status:' << params[:request_latest_status]
+ @match_phrase << _(" filtered by status: '{{status}}'", :status => params[:request_latest_status])
+ end
end
+
@xapian_requests = perform_search([InfoRequestEvent], requests_query, 'newest', 'request_collapse')
@xapian_comments = perform_search([InfoRequestEvent], comments_query, 'newest', nil)
diff --git a/app/helpers/admin_public_body_category_helper.rb b/app/helpers/admin_public_body_category_helper.rb
new file mode 100644
index 000000000..9c5e5cc5e
--- /dev/null
+++ b/app/helpers/admin_public_body_category_helper.rb
@@ -0,0 +1,14 @@
+module AdminPublicBodyCategoryHelper
+ def heading_is_selected?(heading)
+ if params[:headings]
+ if params[:headings]["heading_#{heading.id}"]
+ return true
+ else
+ return false
+ end
+ elsif @category.public_body_headings.include?(heading)
+ return true
+ end
+ false
+ end
+end
diff --git a/app/models/public_body.rb b/app/models/public_body.rb
index 87b5c2227..f61a3f449 100644
--- a/app/models/public_body.rb
+++ b/app/models/public_body.rb
@@ -132,14 +132,14 @@ class PublicBody < ActiveRecord::Base
end
def translated_versions=(translation_attrs)
- def skip?(attrs)
- valueless = attrs.inject({}) { |h, (k, v)| h[k] = v if v != '' and k != 'locale'; h } # because we want to fall back to alternative translations where there are empty values
- return valueless.length == 0
+ def empty_translation?(attrs)
+ attrs_with_values = attrs.select{ |key, value| value != '' and key != 'locale' }
+ attrs_with_values.empty?
end
if translation_attrs.respond_to? :each_value # Hash => updating
translation_attrs.each_value do |attrs|
- next if skip?(attrs)
+ next if empty_translation?(attrs)
t = translation_for(attrs[:locale]) || PublicBody::Translation.new
t.attributes = attrs
calculate_cached_fields(t)
@@ -147,7 +147,7 @@ class PublicBody < ActiveRecord::Base
end
else # Array => creating
translation_attrs.each do |attrs|
- next if skip?(attrs)
+ next if empty_translation?(attrs)
new_translation = PublicBody::Translation.new(attrs)
calculate_cached_fields(new_translation)
translations << new_translation
@@ -335,8 +335,8 @@ class PublicBody < ActiveRecord::Base
types = []
first = true
for tag in self.tags
- if PublicBodyCategories::get().by_tag().include?(tag.name)
- desc = PublicBodyCategories::get().singular_by_tag()[tag.name]
+ if PublicBodyCategory.get().by_tag().include?(tag.name)
+ desc = PublicBodyCategory.get().singular_by_tag()[tag.name]
if first
# terrible that Ruby/Rails doesn't have an equivalent of ucfirst
# (capitalize shockingly converts later characters to lowercase)
diff --git a/app/models/public_body_category.rb b/app/models/public_body_category.rb
new file mode 100644
index 000000000..8eaecd596
--- /dev/null
+++ b/app/models/public_body_category.rb
@@ -0,0 +1,90 @@
+# == Schema Information
+#
+# Table name: public_body_categories
+#
+# id :integer not null, primary key
+# title :text not null
+# category_tag :text not null
+# description :text not null
+# display_order :integer
+#
+
+require 'forwardable'
+
+class PublicBodyCategory < ActiveRecord::Base
+ attr_accessible :locale, :category_tag, :title, :description,
+ :translated_versions, :display_order
+
+ has_many :public_body_category_links, :dependent => :destroy
+ has_many :public_body_headings, :through => :public_body_category_links
+
+ translates :title, :description
+ validates_uniqueness_of :category_tag, :message => N_('Tag is already taken')
+ validates_presence_of :title, :message => N_("Title can't be blank")
+ validates_presence_of :category_tag, :message => N_("Tag can't be blank")
+
+ def self.get
+ locale = I18n.locale.to_s || default_locale.to_s || ""
+ categories = CategoryCollection.new
+ I18n.with_locale(locale) do
+ headings = PublicBodyHeading.all
+ headings.each do |heading|
+ categories << heading.name
+ heading.public_body_categories.each do |category|
+ categories << [
+ category.category_tag,
+ category.title,
+ category.description
+ ]
+ end
+ end
+ end
+ categories
+ end
+
+ def self.without_headings
+ sql = %Q| SELECT * FROM public_body_categories pbc
+ WHERE pbc.id NOT IN (
+ SELECT public_body_category_id AS id
+ FROM public_body_category_links
+ ) |
+ PublicBodyCategory.find_by_sql(sql)
+ end
+
+ # Called from the old-style public_body_categories_[locale].rb data files
+ def self.add(locale, data_list)
+ CategoryAndHeadingMigrator.add_categories_and_headings_from_list(locale, data_list)
+ end
+
+ # Convenience methods for creating/editing translations via forms
+ def find_translation_by_locale(locale)
+ translations.find_by_locale(locale)
+ end
+
+ def translated_versions
+ translations
+ end
+
+ def translated_versions=(translation_attrs)
+ def empty_translation?(attrs)
+ attrs_with_values = attrs.select{ |key, value| value != '' and key != 'locale' }
+ attrs_with_values.empty?
+ end
+ if translation_attrs.respond_to? :each_value # Hash => updating
+ translation_attrs.each_value do |attrs|
+ next if empty_translation?(attrs)
+ t = translation_for(attrs[:locale]) || PublicBodyCategory::Translation.new
+ t.attributes = attrs
+ t.save!
+ end
+ else # Array => creating
+ translation_attrs.each do |attrs|
+ next if empty_translation?(attrs)
+ new_translation = PublicBodyCategory::Translation.new(attrs)
+ translations << new_translation
+ end
+ end
+ end
+end
+
+
diff --git a/app/models/public_body_category/category_collection.rb b/app/models/public_body_category/category_collection.rb
new file mode 100644
index 000000000..8286e2710
--- /dev/null
+++ b/app/models/public_body_category/category_collection.rb
@@ -0,0 +1,54 @@
+# replicate original file-based PublicBodyCategories functionality
+class PublicBodyCategory::CategoryCollection
+ include Enumerable
+ extend Forwardable
+ def_delegators :@categories, :each, :<<
+
+ def initialize
+ @categories = []
+ end
+
+ def with_headings
+ @categories
+ end
+
+ def with_description
+ @categories.select() { |a| a.instance_of?(Array) }
+ end
+
+ def tags
+ tags = with_description.map() { |a| a[0] }
+ end
+
+ def by_tag
+ Hash[*with_description.map() { |a| a[0..1] }.flatten]
+ end
+
+ def singular_by_tag
+ Hash[*with_description.map() { |a| [a[0],a[2]] }.flatten]
+ end
+
+ def by_heading
+ output = {}
+ heading = nil
+ @categories.each do |row|
+ if row.is_a?(Array)
+ output[heading] << row[0]
+ else
+ heading = row
+ output[heading] = []
+ end
+ end
+ output
+ end
+
+ def headings
+ output = []
+ @categories.each do |row|
+ unless row.is_a?(Array)
+ output << row
+ end
+ end
+ output
+ end
+end
diff --git a/app/models/public_body_category_link.rb b/app/models/public_body_category_link.rb
new file mode 100644
index 000000000..eb233b56f
--- /dev/null
+++ b/app/models/public_body_category_link.rb
@@ -0,0 +1,34 @@
+# == Schema Information
+#
+# Table name: public_body_category_link
+#
+# public_body_category_id :integer not null
+# public_body_heading_id :integer not null
+# category_display_order :integer
+#
+
+class PublicBodyCategoryLink < ActiveRecord::Base
+ attr_accessible :public_body_category_id, :public_body_heading_id, :category_display_order
+
+ belongs_to :public_body_category
+ belongs_to :public_body_heading
+ validates_presence_of :public_body_category
+ validates_presence_of :public_body_heading
+ validates :category_display_order, :numericality => { :only_integer => true,
+ :message => N_('Display order must be a number') }
+
+ before_validation :on => :create do
+ unless self.category_display_order
+ self.category_display_order = PublicBodyCategoryLink.next_display_order(public_body_heading_id)
+ end
+ end
+
+ def self.next_display_order(heading_id)
+ if last = where(:public_body_heading_id => heading_id).order(:category_display_order).last
+ last.category_display_order + 1
+ else
+ 0
+ end
+ end
+
+end
diff --git a/app/models/public_body_heading.rb b/app/models/public_body_heading.rb
new file mode 100644
index 000000000..c38800561
--- /dev/null
+++ b/app/models/public_body_heading.rb
@@ -0,0 +1,75 @@
+# == Schema Information
+#
+# Table name: public_body_headings
+#
+# id :integer not null, primary key
+# name :text not null
+# display_order :integer
+#
+
+class PublicBodyHeading < ActiveRecord::Base
+ attr_accessible :name, :display_order, :translated_versions
+
+ has_many :public_body_category_links, :dependent => :destroy
+ has_many :public_body_categories, :order => :category_display_order, :through => :public_body_category_links
+ default_scope order('display_order ASC')
+
+ translates :name
+
+ validates_uniqueness_of :name, :message => N_('Name is already taken')
+ validates_presence_of :name, :message => N_('Name can\'t be blank')
+ validates :display_order, :numericality => { :only_integer => true,
+ :message => N_('Display order must be a number') }
+
+ before_validation :on => :create do
+ unless self.display_order
+ self.display_order = PublicBodyHeading.next_display_order
+ end
+ end
+
+ # Convenience methods for creating/editing translations via forms
+ def find_translation_by_locale(locale)
+ translations.find_by_locale(locale)
+ end
+
+ def translated_versions
+ translations
+ end
+
+ def translated_versions=(translation_attrs)
+ def empty_translation?(attrs)
+ attrs_with_values = attrs.select{ |key, value| value != '' and key != 'locale' }
+ attrs_with_values.empty?
+ end
+
+ if translation_attrs.respond_to? :each_value # Hash => updating
+ translation_attrs.each_value do |attrs|
+ next if empty_translation?(attrs)
+ t = translation_for(attrs[:locale]) || PublicBodyHeading::Translation.new
+ t.attributes = attrs
+ t.save!
+ end
+ else # Array => creating
+ translation_attrs.each do |attrs|
+ next if empty_translation?(attrs)
+ new_translation = PublicBodyHeading::Translation.new(attrs)
+ translations << new_translation
+ end
+ end
+ end
+
+ def add_category(category)
+ unless public_body_categories.include?(category)
+ public_body_categories << category
+ end
+ end
+
+ def self.next_display_order
+ if max = maximum(:display_order)
+ max + 1
+ else
+ 0
+ end
+ end
+
+end
diff --git a/app/views/admin_general/_admin_navbar.html.erb b/app/views/admin_general/_admin_navbar.html.erb
index 5cc740f70..14fc06092 100644
--- a/app/views/admin_general/_admin_navbar.html.erb
+++ b/app/views/admin_general/_admin_navbar.html.erb
@@ -10,6 +10,7 @@
<li><%= link_to 'Stats', admin_stats_path %></li>
<li><%= link_to 'Debug', admin_debug_path %></li>
<li><%= link_to 'Authorities', admin_body_list_path %></li>
+ <li><%= link_to 'Categories', admin_categories_path %></li>
<li><%= link_to 'Requests', admin_request_list_path %></li>
<li><%= link_to 'Users', admin_user_list_path %></li>
<li><%= link_to 'Tracks', admin_track_list_path %></li>
diff --git a/app/views/admin_public_body/_tag_help.html.erb b/app/views/admin_public_body/_tag_help.html.erb
index b64e65877..5d6990400 100644
--- a/app/views/admin_public_body/_tag_help.html.erb
+++ b/app/views/admin_public_body/_tag_help.html.erb
@@ -1,6 +1,6 @@
<h2>List of tags</h2>
<% first_row = true %>
-<% for row in PublicBodyCategories::get().with_headings() %>
+<% for row in PublicBodyCategory.get().with_headings() %>
<% if row.instance_of?(Array) %>
<% if row[0] != 'other' %>
<strong><%= row[0] %></strong>=<%= row[1] %>
diff --git a/app/views/admin_public_body/import_csv.html.erb b/app/views/admin_public_body/import_csv.html.erb
index c690f0fc2..4b14226d1 100644
--- a/app/views/admin_public_body/import_csv.html.erb
+++ b/app/views/admin_public_body/import_csv.html.erb
@@ -76,7 +76,7 @@ Another One,another@example.com,Otro organismo,a_tag
<hr>
<p>Standard tags:
- <% for category, description in PublicBodyCategories::get().by_tag() %>
+ <% for category, description in PublicBodyCategory.get().by_tag() %>
<% if category != "other" %>
<strong><%= category %></strong>=<%= description %>;
<% end %>
diff --git a/app/views/admin_public_body_categories/_category_list_item.html.erb b/app/views/admin_public_body_categories/_category_list_item.html.erb
new file mode 100644
index 000000000..056ab6148
--- /dev/null
+++ b/app/views/admin_public_body_categories/_category_list_item.html.erb
@@ -0,0 +1,5 @@
+<div class="category-list-item" <% if heading %> data-id="categories_<%= category.id %>"<% end %>>
+ <%= link_to(category.title, edit_admin_category_path(category), :title => "view full details") %>
+</div>
+
+
diff --git a/app/views/admin_public_body_categories/_form.html.erb b/app/views/admin_public_body_categories/_form.html.erb
new file mode 100644
index 000000000..b0778d371
--- /dev/null
+++ b/app/views/admin_public_body_categories/_form.html.erb
@@ -0,0 +1,66 @@
+<%= error_messages_for 'category' %>
+
+<!--[form:public_body_category]-->
+
+<div id="div-locales">
+ <ul class="locales nav nav-tabs">
+ <% I18n.available_locales.each_with_index do |locale, i| %>
+ <li><a href="#div-locale-<%=locale.to_s%>" data-toggle="tab" ><%=locale_name(locale.to_s) || "Default locale"%></a></li>
+ <% end %>
+ </ul>
+ <div class="tab-content">
+<%
+ I18n.available_locales.each do |locale|
+ if locale==I18n.default_locale # The default locale is submitted as part of the bigger object...
+ prefix = 'public_body_category'
+ object = @category
+ else # ...but additional locales go "on the side"
+ prefix = "public_body_category[translated_versions][]"
+ object = @category.new_record? ?
+ PublicBodyCategory::Translation.new :
+ @category.find_translation_by_locale(locale.to_s) || PublicBodyCategory::Translation.new
+ end
+%>
+ <%= fields_for prefix, object do |t| %>
+ <div class="tab-pane" id="div-locale-<%=locale.to_s%>">
+ <div class="control-group">
+ <%= t.hidden_field :locale, :value => locale.to_s %>
+ <label for="<%= form_tag_id(t.object_name, :title, locale) %>" class="control-label">Title</label>
+ <div class="controls">
+ <%= t.text_field :title, :id => form_tag_id(t.object_name, :title, locale), :class => "span4" %>
+ </div>
+ </div>
+ <div class="control-group">
+ <label for="<%= form_tag_id(t.object_name, :description, locale) %>" class="control-label">Description</label>
+ <div class="controls">
+ <%= t.text_field :description, :id => form_tag_id(t.object_name, :description, locale), :class => "span4" %>
+ </div>
+ </div>
+ </div>
+ <%
+ end
+end
+%>
+ </div>
+</div>
+
+<% if PublicBody.find_by_tag(@category.category_tag).count == 0 or @category.errors.messages.keys.include?(:category_tag) %>
+ <h3>Common Fields</h3>
+
+ <div class="control-group">
+ <label for="public_body_category_category_tag" class="control-label">Category tag</label>
+ <div class="controls">
+ <%= f.text_field :category_tag, :class => "span4" %>
+ </div>
+ </div>
+<% end %>
+
+<h3>Headings</h3>
+<div class="control-group">
+ <% PublicBodyHeading.all.each do |heading| %>
+ <div class="span3">
+ &nbsp;<%= check_box_tag "headings[heading_#{heading.id}]", heading.id, heading_is_selected?(heading) %> <label for="headings_heading_<%= heading.id %>" class="control-label"><%= heading.name %></label>
+ </div>
+ <% end %>
+</div>
+<!--[eoform:public_body_category]-->
diff --git a/app/views/admin_public_body_categories/_heading_list.html.erb b/app/views/admin_public_body_categories/_heading_list.html.erb
new file mode 100644
index 000000000..4bd8bdc90
--- /dev/null
+++ b/app/views/admin_public_body_categories/_heading_list.html.erb
@@ -0,0 +1,26 @@
+<div class="accordion" id="category_list">
+ <% for heading in category_headings %>
+ <div class="accordion-group" data-id="headings_<%=heading.id%>">
+ <div class="accordion-heading accordion-toggle row">
+ <span class="item-title span6">
+ <a href="#heading_<%=heading.id%>_categories" data-toggle="collapse" data-parent="#categories" ><%= chevron_right %></a>
+ <strong><%= link_to(heading.name, edit_admin_heading_path(heading), :title => "view full details") %></strong>
+ </span>
+ </div>
+
+ <div id="heading_<%= heading.id %>_categories" class="accordion-body collapse row ">
+ <div class="well">
+ <div class="span12" id="heading_<%= heading.id %>_category_list" class="category-list">
+ <% heading.public_body_categories.each do |category| %>
+ <%= render :partial => 'category_list_item', :locals => { :category => category, :heading => heading } %>
+ <% end %>
+ </div>
+
+ <div class="form-actions save-panel">
+ <%= link_to "Save", '#', :class => "btn btn-primary disabled save-order", "data-heading-id" => heading.id, "data-list-id" => "#heading_#{heading.id}_category_list", 'data-endpoint' => reorder_categories_admin_heading_path(heading) %><p class="save-notice" data-unsaved-text="There are unsaved changes to the order of categories." data-success-text="Changes saved." data-error-text="There was an error saving your changes: ">Drag and drop to change the order of categories.</p>
+ </div>
+ </div>
+ </div>
+ </div>
+ <% end %>
+</div>
diff --git a/app/views/admin_public_body_categories/edit.html.erb b/app/views/admin_public_body_categories/edit.html.erb
new file mode 100644
index 000000000..95988d688
--- /dev/null
+++ b/app/views/admin_public_body_categories/edit.html.erb
@@ -0,0 +1,30 @@
+<h1><%=@title%></h1>
+
+<div class="row">
+ <div class="span8">
+ <div id="public_body_category_form">
+ <%= form_for @category, :url => admin_category_path(@category), :html => { :class => "form form-horizontal" } do |f| %>
+ <%= render :partial => 'form', :locals => {:f => f} %>
+ <div class="form-actions">
+ <%= f.submit 'Save', :accesskey => 's', :class => "btn btn-success" %></p>
+ </div>
+ <% end %>
+ </div>
+</div>
+
+<div class="row">
+ <div class="span8 well">
+ <%= link_to 'List all', admin_categories_path, :class => "btn" %>
+ </div>
+</div>
+
+<% if @tagged_public_bodies.empty? %>
+ <div class="row">
+ <div class="span8">
+ <%= form_tag(admin_category_path(@category), :method => 'delete', :class => "form form-inline") do %>
+ <%= hidden_field_tag(:public_body_id, { :value => @category.id } ) %>
+ <%= submit_tag "Destroy #{@category.title}", :title => @category.title, :class => "btn btn-danger" %> (this is permanent!)
+ <% end %>
+ </div>
+ </div>
+<% end %>
diff --git a/app/views/admin_public_body_categories/index.html.erb b/app/views/admin_public_body_categories/index.html.erb
new file mode 100644
index 000000000..62ec4623d
--- /dev/null
+++ b/app/views/admin_public_body_categories/index.html.erb
@@ -0,0 +1,28 @@
+<% @title = 'Listing public authority categories' %>
+
+<h1><%=@title%></h1>
+
+<div class="btn-toolbar">
+ <div class="btn-group">
+ <%= link_to 'New category', new_admin_category_path, :class => "btn btn-primary" %>
+ </div>
+ <div class="btn-group">
+ <%= link_to 'New category heading', new_admin_heading_path, :class => "btn" %>
+ </div>
+</div>
+
+<h2>All category headings</h2>
+<div>
+<%= render :partial => 'heading_list', :locals => { :category_headings => @category_headings, :table_name => 'exact' } %>
+
+<% if @without_heading.count > 0 %>
+
+ <h3>Categories with no heading</h3>
+
+ <% @without_heading.each do |category| %>
+ <%= render :partial => 'category_list_item', :locals => { :category => category, :heading => nil } %>
+ <% end %>
+<% end %>
+<div class="form-actions save-panel">
+<%= link_to "Save", '#', :class => "btn btn-primary disabled save-order", "data-list-id" => '#category_list', 'data-endpoint' => reorder_admin_headings_path %><p class="save-notice" data-unsaved-text="There are unsaved changes to the order of category headings." data-success-text="Changes saved." data-error-text="There was an error saving your changes: ">Drag and drop to change the order of category headings.</p>
+</div>
diff --git a/app/views/admin_public_body_categories/new.html.erb b/app/views/admin_public_body_categories/new.html.erb
new file mode 100644
index 000000000..8b1b1103f
--- /dev/null
+++ b/app/views/admin_public_body_categories/new.html.erb
@@ -0,0 +1,21 @@
+<% @title = 'New category' %>
+
+<h1><%=@title%></h1>
+<div class="row">
+ <div class="span8">
+ <div id="public_category_form">
+ <%= form_for @category, :url => admin_categories_path, :html => {:class => "form form-horizontal"} do |f| %>
+ <%= render :partial => 'form', :locals => {:f => f} %>
+
+ <div class="form-actions">
+ <%= f.submit "Create", :class => "btn btn-primary" %>
+ </div>
+ <% end %>
+ <div class="row">
+ <div class="span8 well">
+ <%= link_to 'List all', admin_categories_path, :class => "btn" %>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/views/admin_public_body_headings/_form.html.erb b/app/views/admin_public_body_headings/_form.html.erb
new file mode 100644
index 000000000..d4e914ca1
--- /dev/null
+++ b/app/views/admin_public_body_headings/_form.html.erb
@@ -0,0 +1,41 @@
+<%= error_messages_for 'heading' %>
+
+<!--[form:public_body_heading]-->
+
+<div id="div-locales">
+ <ul class="locales nav nav-tabs">
+ <% I18n.available_locales.each_with_index do |locale, i| %>
+ <li><a href="#div-locale-<%=locale.to_s%>" data-toggle="tab" ><%=locale_name(locale.to_s) || "Default locale"%></a></li>
+ <% end %>
+ </ul>
+ <div class="tab-content">
+<%
+ for locale in I18n.available_locales do
+ if locale==I18n.default_locale # The default locale is submitted as part of the bigger object...
+ prefix = 'public_body_heading'
+ object = @heading
+ else # ...but additional locales go "on the side"
+ prefix = "public_body_heading[translated_versions][]"
+ object = @heading.new_record? ?
+ PublicBodyHeading::Translation.new :
+ @heading.find_translation_by_locale(locale.to_s) || PublicBodyHeading::Translation.new
+ end
+%>
+ <%= fields_for prefix, object do |t| %>
+ <div class="tab-pane" id="div-locale-<%=locale.to_s%>">
+ <div class="control-group">
+ <%= t.hidden_field :locale, :value => locale.to_s %>
+ <label for="<%= form_tag_id(t.object_name, :name, locale) %>" class="control-label">Name</label>
+ <div class="controls">
+ <%= t.text_field :name, :id => form_tag_id(t.object_name, :name, locale), :class => "span4" %>
+ </div>
+ </div>
+ </div>
+ <%
+ end
+end
+%>
+ </div>
+</div>
+
+<!--[eoform:public_body_heading]-->
diff --git a/app/views/admin_public_body_headings/edit.html.erb b/app/views/admin_public_body_headings/edit.html.erb
new file mode 100644
index 000000000..eff89285a
--- /dev/null
+++ b/app/views/admin_public_body_headings/edit.html.erb
@@ -0,0 +1,30 @@
+<h1><%=@title%></h1>
+
+<div class="row">
+ <div class="span8">
+ <div id="public_body_heading_form">
+ <%= form_for @heading, :url => admin_heading_path(@heading), :html => { :class => "form form-horizontal" } do |f| %>
+ <%= render :partial => 'form', :locals => {:f => f} %>
+ <div class="form-actions">
+ <%= f.submit 'Save', :accesskey => 's', :class => "btn btn-success" %></p>
+ </div>
+ <% end %>
+ </div>
+</div>
+
+<div class="row">
+ <div class="span8 well">
+ <%= link_to 'List all', admin_categories_path, :class => "btn" %>
+ </div>
+</div>
+
+<% if @heading.public_body_categories.empty? %>
+ <div class="row">
+ <div class="span8">
+ <%= form_tag(admin_heading_path(@heading), :method => 'delete', :class => "form form-inline") do %>
+ <%= hidden_field_tag(:public_body_heading_id, { :value => @heading.id } ) %>
+ <%= submit_tag "Destroy #{@heading.name}", :name => @heading.name, :class => "btn btn-danger" %> (this is permanent!)
+ <% end %>
+ </div>
+ </div>
+<% end %>
diff --git a/app/views/admin_public_body_headings/new.html.erb b/app/views/admin_public_body_headings/new.html.erb
new file mode 100644
index 000000000..91d5d4a9d
--- /dev/null
+++ b/app/views/admin_public_body_headings/new.html.erb
@@ -0,0 +1,21 @@
+<% @title = 'New category heading' %>
+
+<h1><%=@title%></h1>
+<div class="row">
+ <div class="span8">
+ <div id="public_heading_form">
+ <%= form_for @heading, :url => admin_headings_path, :html => {:class => "form form-horizontal"} do |f| %>
+ <%= render :partial => 'form', :locals => {:f => f} %>
+
+ <div class="form-actions">
+ <%= f.submit "Create", :class => "btn btn-primary" %>
+ </div>
+ <% end %>
+ <div class="row">
+ <div class="span8 well">
+ <%= link_to 'List all', admin_categories_path, :class => "btn" %>
+ </div>
+ </div>
+ </div>
+ </div>
+</div>
diff --git a/app/views/admin_request/_incoming_message_actions.html.erb b/app/views/admin_request/_incoming_message_actions.html.erb
index dd50eb047..22effcce5 100644
--- a/app/views/admin_request/_incoming_message_actions.html.erb
+++ b/app/views/admin_request/_incoming_message_actions.html.erb
@@ -25,7 +25,7 @@
<div class="control-group">
<label class="control-label">Mark <code>To:</code> address as spam</label>
<div class="controls">
- <%= link_to 'Spam Addresses', spam_addresses_path %>
+ <%= link_to 'Spam Addresses', admin_spam_addresses_path %>
</div>
</div>
diff --git a/app/views/admin_spam_addresses/index.html.erb b/app/views/admin_spam_addresses/index.html.erb
index 9846bc017..7a11f70e1 100644
--- a/app/views/admin_spam_addresses/index.html.erb
+++ b/app/views/admin_spam_addresses/index.html.erb
@@ -16,7 +16,7 @@
<div class="row">
<div class="span12">
- <%= form_for(@spam_address, :html => { :class => 'form-inline' }) do |f| -%>
+ <%= form_for(@spam_address, :url => admin_spam_addresses_path, :html => { :class => 'form-inline' }) do |f| -%>
<%= error_messages_for @spam_address %>
<%= f.text_field :email, :class => 'input-xxlarge', :placeholder => 'Enter email' %>
<%= f.submit 'Add Spam Address', :class => 'btn btn-warning' %>
@@ -39,7 +39,7 @@
<% @spam_addresses.each do |spam| %>
<tr>
<td><%= spam.email %></td>
- <td><%= link_to 'Remove', spam,
+ <td><%= link_to 'Remove', admin_spam_address_path(spam),
:method => :delete,
:confirm => 'This is permanent! Are you sure?',
:class => 'btn btn-mini btn-danger' %></td>
diff --git a/app/views/public_body/list.html.erb b/app/views/public_body/list.html.erb
index ce24daaf9..0750c7655 100644
--- a/app/views/public_body/list.html.erb
+++ b/app/views/public_body/list.html.erb
@@ -7,7 +7,7 @@
</li>
</ul>
<% first_row = true %>
- <% for row in PublicBodyCategories::get().with_headings() %>
+ <% for row in PublicBodyCategory.get().with_headings() %>
<% if row.instance_of?(Array) %>
<li>
<%= link_to_unless (@tag == row[0]), row[1], list_public_bodies_path(:tag => row[0]) %>
diff --git a/app/views/user/show.html.erb b/app/views/user/show.html.erb
index 7ae577565..b23f74326 100644
--- a/app/views/user/show.html.erb
+++ b/app/views/user/show.html.erb
@@ -121,6 +121,9 @@
<%= form_tag(show_user_url, :method => "get", :id=>"search_form") do %>
<div>
<%= text_field_tag(:user_query, params[:user_query], {:title => "type your search term here" }) %>
+ <%= select_tag :request_latest_status,
+ options_from_collection_for_select(@request_states, 'value', 'text', params[:request_latest_status]),
+ :prompt => _('Filter by Request Status (optional)') %>
<% if @is_you %>
<%= submit_tag(_("Search your contributions")) %>
<% else %>
diff --git a/config/initializers/alaveteli.rb b/config/initializers/alaveteli.rb
index 9ea6428ba..9a151e3e8 100644
--- a/config/initializers/alaveteli.rb
+++ b/config/initializers/alaveteli.rb
@@ -44,7 +44,6 @@ require 'world_foi_websites.rb'
require 'alaveteli_external_command.rb'
require 'quiet_opener.rb'
require 'mail_handler'
-require 'public_body_categories'
require 'ability'
require 'normalize_string'
require 'alaveteli_file_types'
@@ -54,6 +53,8 @@ require 'theme'
require 'xapian_queries'
require 'date_quarter'
require 'public_body_csv'
+require 'category_and_heading_migrator'
+require 'public_body_categories'
AlaveteliLocalization.set_locales(AlaveteliConfiguration::available_locales,
AlaveteliConfiguration::default_locale)
@@ -62,3 +63,4 @@ AlaveteliLocalization.set_locales(AlaveteliConfiguration::available_locales,
if Rails.env == 'test' and ActiveRecord::Base.configurations['test']['constraint_disabling'] == false
require 'no_constraint_disabling'
end
+
diff --git a/config/routes.rb b/config/routes.rb
index 84ec86792..ff99e884c 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -181,6 +181,24 @@ Alaveteli::Application.routes.draw do
match '/admin/body/mass_tag_add' => 'admin_public_body#mass_tag_add', :as => :admin_body_mass_tag_add
####
+ #### AdminPublicBodyCategory controller
+ scope '/admin', :as => 'admin' do
+ resources :categories,
+ :controller => 'admin_public_body_categories'
+ end
+ ####
+
+ #### AdminPublicBodyHeading controller
+ scope '/admin', :as => 'admin' do
+ resources :headings,
+ :controller => 'admin_public_body_headings',
+ :except => [:index] do
+ post 'reorder', :on => :collection
+ post 'reorder_categories', :on => :member
+ end
+ end
+ ####
+
#### AdminPublicBodyChangeRequest controller
match '/admin/change_request/edit/:id' => 'admin_public_body_change_requests#edit', :as => :admin_change_request_edit
match '/admin/change_request/update/:id' => 'admin_public_body_change_requests#update', :as => :admin_change_request_update
@@ -252,7 +270,7 @@ Alaveteli::Application.routes.draw do
####
#### AdminSpamAddresses controller
- scope '/admin' do
+ scope '/admin', :as => 'admin' do
resources :spam_addresses,
:controller => 'admin_spam_addresses',
:only => [:index, :create, :destroy]
diff --git a/db/migrate/20140710094405_create_public_body_headings_and_categories.rb b/db/migrate/20140710094405_create_public_body_headings_and_categories.rb
new file mode 100644
index 000000000..0ba7f64a0
--- /dev/null
+++ b/db/migrate/20140710094405_create_public_body_headings_and_categories.rb
@@ -0,0 +1,27 @@
+class CreatePublicBodyHeadingsAndCategories < ActiveRecord::Migration
+ def up
+ create_table :public_body_headings, :force => true do |t|
+ t.string :locale
+ t.text :name, :null => false
+ t.integer :display_order
+ end
+
+ create_table :public_body_categories, :force => true do |t|
+ t.string :locale
+ t.text :title, :null => false
+ t.text :category_tag, :null => false
+ t.text :description, :null => false
+ end
+
+ create_table :public_body_categories_public_body_headings, :id => false do |t|
+ t.integer :public_body_category_id, :null => false
+ t.integer :public_body_heading_id, :null => false
+ end
+ end
+
+ def down
+ drop_table :public_body_categories
+ drop_table :public_body_headings
+ drop_table :public_body_categories_public_body_headings
+ end
+end \ No newline at end of file
diff --git a/db/migrate/20140716131107_create_category_translation_tables.rb b/db/migrate/20140716131107_create_category_translation_tables.rb
new file mode 100644
index 000000000..f4b90b330
--- /dev/null
+++ b/db/migrate/20140716131107_create_category_translation_tables.rb
@@ -0,0 +1,163 @@
+class CreateCategoryTranslationTables < ActiveRecord::Migration
+ class PublicBodyCategory < ActiveRecord::Base
+ translates :title, :description
+ end
+ class PublicBodyHeading < ActiveRecord::Base
+ translates :name
+ end
+ def up
+ default_locale = I18n.locale.to_s
+
+ fields = {:title => :text,
+ :description => :text}
+ PublicBodyCategory.create_translation_table!(fields)
+
+ # copy current values across to the default locale
+ PublicBodyCategory.where(:locale => default_locale).each do |category|
+ category.translated_attributes.each do |a, default|
+ value = category.read_attribute(a)
+ unless value.nil?
+ category.send(:"#{a}=", value)
+ end
+ end
+ category.save!
+ end
+
+ # copy current values across to the non-default locale(s)
+ PublicBodyCategory.where('locale != ?', default_locale).each do |category|
+ default_category = PublicBodyCategory.find_by_category_tag_and_locale(category.category_tag, default_locale)
+ I18n.with_locale(category.locale) do
+ category.translated_attributes.each do |a, default|
+ value = category.read_attribute(a)
+ unless value.nil?
+ if default_category
+ default_category.send(:"#{a}=", value)
+ else
+ category.send(:"#{a}=", value)
+ end
+ end
+ category.delete if default_category
+ end
+ end
+ if default_category
+ default_category.save!
+ category.delete
+ else
+ category.save!
+ end
+ end
+
+ fields = { :name => :text }
+ PublicBodyHeading.create_translation_table!(fields)
+
+ # copy current values across to the default locale
+ PublicBodyHeading.where(:locale => default_locale).each do |heading|
+ heading.translated_attributes.each do |a, default|
+ value = heading.read_attribute(a)
+ unless value.nil?
+ heading.send(:"#{a}=", value)
+ end
+ end
+ heading.save!
+ end
+
+ # copy current values across to the non-default locale(s)
+ PublicBodyHeading.where('locale != ?', default_locale).each do |heading|
+ default_heading = PublicBodyHeading.find_by_name_and_locale(heading.name, default_locale)
+ I18n.with_locale(heading.locale) do
+ heading.translated_attributes.each do |a, default|
+ value = heading.read_attribute(a)
+ unless value.nil?
+ if default_heading
+ default_heading.send(:"#{a}=", value)
+ else
+ heading.send(:"#{a}=", value)
+ end
+ end
+ heading.delete if default_heading
+ end
+ end
+ if default_heading
+ default_heading.save!
+ heading.delete
+ else
+ heading.save!
+ end
+ end
+
+ # finally, drop the old locale column from both tables
+ remove_column :public_body_headings, :locale
+ remove_column :public_body_categories, :locale
+ remove_column :public_body_headings, :name
+ remove_column :public_body_categories, :title
+ remove_column :public_body_categories, :description
+
+ # and set category_tag to be unique
+ add_index :public_body_categories, :category_tag, :unique => true
+ end
+
+ def down
+ # reinstate the columns
+ add_column :public_body_categories, :locale, :string
+ add_column :public_body_headings, :locale, :string
+ add_column :public_body_headings, :name, :string
+ add_column :public_body_categories, :title, :string
+ add_column :public_body_categories, :description, :string
+
+ # drop the index
+ remove_index :public_body_categories, :category_tag
+
+ # restore the data
+ new_categories = []
+ PublicBodyCategory.all.each do |category|
+ category.locale = category.translation.locale.to_s
+ I18n.available_locales.each do |locale|
+ if locale.to_s != category.locale
+ translation = category.translations.find_by_locale(locale)
+ if translation
+ new_cat = category.dup
+ category.translated_attributes.each do |a, _|
+ value = translation.read_attribute(a)
+ new_cat.send(:"#{a}=", value)
+ end
+ new_cat.locale = locale.to_s
+ new_categories << new_cat
+ end
+ else
+ category.save!
+ end
+ end
+ end
+ new_categories.each do |cat|
+ cat.save!
+ end
+
+ new_headings = []
+ PublicBodyHeading.all.each do |heading|
+ heading.locale = heading.translation.locale.to_s
+ I18n.available_locales.each do |locale|
+ if locale.to_s != heading.locale
+ new_heading = heading.dup
+ translation = heading.translations.find_by_locale(locale)
+ if translation
+ heading.translated_attributes.each do |a, _|
+ value = translation.read_attribute(a)
+ new_heading.send(:"#{a}=", value)
+ end
+ new_heading.locale = locale.to_s
+ new_headings << new_heading
+ end
+ else
+ heading.save!
+ end
+ end
+ end
+ new_headings.each do |heading|
+ heading.save!
+ end
+
+ # drop the translation tables
+ PublicBodyCategory.drop_translation_table!
+ PublicBodyHeading.drop_translation_table!
+ end
+end
diff --git a/db/migrate/20140804120601_add_display_order_to_categories_and_headings.rb b/db/migrate/20140804120601_add_display_order_to_categories_and_headings.rb
new file mode 100644
index 000000000..c2e7e2ac3
--- /dev/null
+++ b/db/migrate/20140804120601_add_display_order_to_categories_and_headings.rb
@@ -0,0 +1,15 @@
+class AddDisplayOrderToCategoriesAndHeadings < ActiveRecord::Migration
+ def up
+ add_column :public_body_categories_public_body_headings, :category_display_order, :integer
+ rename_table :public_body_categories_public_body_headings, :public_body_category_links
+ add_column :public_body_category_links, :id, :primary_key
+ add_index :public_body_category_links, [:public_body_category_id, :public_body_heading_id], :name => "index_public_body_category_links_on_join_ids", :primary => true
+ end
+
+ def down
+ remove_index :public_body_category_links, :name => "index_public_body_category_links_on_join_ids"
+ remove_column :public_body_category_links, :category_display_order
+ remove_column :public_body_category_links, :id
+ rename_table :public_body_category_links, :public_body_categories_public_body_headings
+ end
+end
diff --git a/doc/CHANGES.md b/doc/CHANGES.md
index 748b37665..40834d4d0 100644
--- a/doc/CHANGES.md
+++ b/doc/CHANGES.md
@@ -8,6 +8,8 @@
rather than only as a database constraint. If you have added a `CensorRule` in
your theme, you will now have to satisfy the additional validations on the
`:replacement`, `:last_edit_comment` and `:last_edit_editor` attributes.
+* Public body categories will now be stored in the database rather than being read directly from the `lib/public_body_categories_LOCALE` files. Once you have upgraded, run `script/migrate-public-body-categories`to import the contents of the files into the database. All further changes will then need to be made via the administrative interface. You can then remove any `pubic_body_categories_[locale].rb` files from your theme. If your theme has any calls to `PublicBodyCategories` methods outside these files, you should update them to call the corresponding method on `PublicBodyCategory` instead.
+
# Version 0.19
@@ -69,11 +71,11 @@ candidate:
* Install `lockfile-progs` so that the `run-with-lockfile` shell script can be
used instead of the C program
-* Use responsive stylesheets in `config/general.yml`:
+* Use responsive stylesheets in `config/general.yml`:
`RESPONSIVE_STYLING: true`. If you don't currently use responsive styling,
and you don't want to get switched over just set `RESPONSIVE_STYLING: false`
and the fixed-width stylesheets will be used as before.
-* Allow access to public body stats page if desired in `config/general/yml`:
+* Allow access to public body stats page if desired in `config/general/yml`:
`PUBLIC_BODY_STATISTICS_PAGE: true`
* Run migrations to define track_things constraint correctly (Robin Houston) and
add additional index for `event_type` on `info_request_events` (Steven Day)
diff --git a/lib/category_and_heading_migrator.rb b/lib/category_and_heading_migrator.rb
new file mode 100644
index 000000000..402ea7204
--- /dev/null
+++ b/lib/category_and_heading_migrator.rb
@@ -0,0 +1,91 @@
+module CategoryAndHeadingMigrator
+
+ # This module migrates data from public_body_categories_[locale].rb files
+ # into PublicBodyHeading and PublicBodyCategory models
+
+ # Load all the data from public_body_categories_[locale].rb files.
+ def self.migrate_categories_and_headings
+ if PublicBodyCategory.count > 0
+ puts "PublicBodyCategories exist already, not migrating."
+ else
+ @first_locale = true
+ I18n.available_locales.each do |locale|
+ begin
+ load "public_body_categories_#{locale}.rb"
+ rescue MissingSourceFile
+ end
+ @first_locale = false
+ end
+ end
+ end
+
+ # Load the categories and headings for a locale
+ def self.add_categories_and_headings_from_list(locale, data_list)
+ # set the counter for headings loaded from this locale
+ @@locale_heading_display_order = 0
+ current_heading = nil
+ data_list.each do |list_item|
+ if list_item.is_a?(Array)
+ # item is list of category data
+ add_category(list_item, current_heading, locale)
+ else
+ # item is heading name
+ current_heading = add_heading(list_item, locale, @first_locale)
+ end
+ end
+ end
+
+ def self.add_category(category_data, heading, locale)
+ tag, title, description = category_data
+ category = PublicBodyCategory.find_by_category_tag(tag)
+ if category
+ add_category_in_locale(category, title, description, locale)
+ else
+ category = PublicBodyCategory.create(:category_tag => tag,
+ :title => title,
+ :description => description)
+
+ # add the translation if this is not the default locale
+ # (occurs when a category is not defined in default locale)
+ unless category.translations.map { |t| t.locale }.include?(locale)
+ add_category_in_locale(category, title, description, locale)
+ end
+ end
+ heading.add_category(category)
+ end
+
+ def self.add_category_in_locale(category, title, description, locale)
+ I18n.with_locale(locale) do
+ category.title = title
+ category.description = description
+ category.save
+ end
+ end
+
+ def self.add_heading(name, locale, first_locale)
+ heading = nil
+ I18n.with_locale(locale) do
+ heading = PublicBodyHeading.find_by_name(name)
+ end
+ # For multi-locale installs, we assume that all public_body_[locale].rb files
+ # use the same headings in the same order, so we add translations to the heading
+ # that was in the same position in the list loaded from other public_body_[locale].rb
+ # files.
+ if heading.nil? && !@first_locale
+ heading = PublicBodyHeading.where(:display_order => @@locale_heading_display_order).first
+ end
+
+ if heading
+ I18n.with_locale(locale) do
+ heading.name = name
+ heading.save
+ end
+ else
+ I18n.with_locale(locale) do
+ heading = PublicBodyHeading.create(:name => name)
+ end
+ end
+ @@locale_heading_display_order += 1
+ heading
+ end
+end
diff --git a/lib/public_body_categories.rb b/lib/public_body_categories.rb
index 7f548b130..3528e85b1 100644
--- a/lib/public_body_categories.rb
+++ b/lib/public_body_categories.rb
@@ -1,60 +1,11 @@
-# lib/public_body_categories.rb:
-# Categorisations of public bodies.
-#
-# Copyright (c) 2009 UK Citizens Online Democracy. All rights reserved.
-# Email: hello@mysociety.org; WWW: http://www.mysociety.org/
-
+# Allow the PublicBodyCategory model to be addressed using the same syntax
+# as the old PublicBodyCategories class without needing to rename everything,
+# make sure we're not going to break any themes
class PublicBodyCategories
- attr_reader :with_description,
- :with_headings,
- :tags,
- :by_tag,
- :singular_by_tag,
- :by_heading,
- :headings
-
- def initialize(categories)
- @with_headings = categories
- # Arranged in different ways for different sorts of displaying
- @with_description = @with_headings.select() { |a| a.instance_of?(Array) }
- @tags = @with_description.map() { |a| a[0] }
- @by_tag = Hash[*@with_description.map() { |a| a[0..1] }.flatten]
- @singular_by_tag = Hash[*@with_description.map() { |a| [a[0],a[2]] }.flatten]
- @by_heading = {}
- heading = nil
- @headings = []
- @with_headings.each do |row|
- if ! row.instance_of?(Array)
- heading = row
- @headings << row
- @by_heading[row] = []
- else
- @by_heading[heading] << row[0]
- end
- end
- end
-
-
- def PublicBodyCategories.get
- load_categories if @@CATEGORIES.empty?
- @@CATEGORIES[I18n.locale.to_s] || @@CATEGORIES[I18n.default_locale.to_s] || PublicBodyCategories.new([])
+ def self.method_missing(method, *args, &block)
+ warn 'Use of PublicBodyCategories is deprecated and will be removed in release 0.21. Please use PublicBodyCategory instead.'
+ PublicBodyCategory.send(method, *args, &block)
end
- # Called from the data files themselves
- def PublicBodyCategories.add(locale, categories)
- @@CATEGORIES[locale.to_s] = PublicBodyCategories.new(categories)
- end
-
- private
- @@CATEGORIES = {}
-
- def PublicBodyCategories.load_categories()
- I18n.available_locales.each do |locale|
- begin
- load "public_body_categories_#{locale}.rb"
- rescue MissingSourceFile
- end
- end
- end
end
diff --git a/lib/public_body_categories_en.rb b/lib/public_body_categories_en.rb
deleted file mode 100644
index 95eed750b..000000000
--- a/lib/public_body_categories_en.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-# The PublicBodyCategories structure works like this:
-# [
-# "Main category name",
-# [ "tag_to_use_as_category", "Sub category title", "sentence that can describes things in this subcategory" ],
-# [ "another_tag", "Second sub category title", "another descriptive sentence for things in this subcategory"],
-# "Another main category name",
-# [ "another_tag_2", "Another sub category title", "another descriptive sentence"]
-# ])
-#
-# DO NOT EDIT THIS FILE! It should be overridden in a custom theme.
-# See doc/THEMES.md for more info
-
-PublicBodyCategories.add(:en, [
- "Silly ministries",
- [ "useless_agency", "Useless ministries", "a useless ministry" ],
- [ "lonely_agency", "Lonely agencies", "a lonely agency"],
- "Popular agencies",
- [ "popular_agency", "Popular agencies", "a lonely agency"]
-])
diff --git a/script/migrate-public-body-categories b/script/migrate-public-body-categories
new file mode 100755
index 000000000..23abe4648
--- /dev/null
+++ b/script/migrate-public-body-categories
@@ -0,0 +1,4 @@
+#!/bin/bash
+TOP_DIR="$(dirname "$BASH_SOURCE")/.."
+cd "$TOP_DIR"
+bundle exec rails runner 'CategoryAndHeadingMigrator.migrate_categories_and_headings'
diff --git a/spec/controllers/admin_public_body_categories_controller_spec.rb b/spec/controllers/admin_public_body_categories_controller_spec.rb
new file mode 100644
index 000000000..35454990d
--- /dev/null
+++ b/spec/controllers/admin_public_body_categories_controller_spec.rb
@@ -0,0 +1,192 @@
+require 'spec_helper'
+
+describe AdminPublicBodyCategoriesController do
+ context 'when showing the index of categories and headings' do
+ render_views
+
+ it 'shows the index page' do
+ get :index
+ end
+ end
+
+ context 'when showing the form for a new public body category' do
+ it 'should assign a new public body category to the view' do
+ get :new
+ assigns[:category].should be_a(PublicBodyCategory)
+ end
+ end
+
+ context 'when creating a public body category' do
+ it "creates a new public body category in one locale" do
+ n = PublicBodyCategory.count
+ post :create, {
+ :public_body_category => {
+ :title => 'New Category',
+ :category_tag => 'new_test_category',
+ :description => 'New category for testing stuff'
+ }
+ }
+ PublicBodyCategory.count.should == n + 1
+
+ category = PublicBodyCategory.find_by_title("New Category")
+ response.should redirect_to(admin_categories_path)
+ end
+
+ it "saves the public body category's heading associations" do
+ heading = FactoryGirl.create(:public_body_heading)
+ category_attributes = FactoryGirl.attributes_for(:public_body_category)
+ post :create, {
+ :public_body_category => category_attributes,
+ :headings => {"heading_#{heading.id}" => heading.id}
+ }
+ request.flash[:notice].should include('successful')
+ category = PublicBodyCategory.find_by_title(category_attributes[:title])
+ category.public_body_headings.should == [heading]
+ end
+
+
+ it 'creates a new public body category with multiple locales' do
+ n = PublicBodyCategory.count
+ post :create, {
+ :public_body_category => {
+ :title => 'New Category',
+ :category_tag => 'new_test_category',
+ :description => 'New category for testing stuff',
+ :translated_versions => [{ :locale => "es",
+ :title => "Mi Nuevo Category" }]
+ }
+ }
+ PublicBodyCategory.count.should == n + 1
+
+ category = PublicBodyCategory.find_by_title("New Category")
+ category.translations.map {|t| t.locale.to_s}.sort.should == ["en", "es"]
+ I18n.with_locale(:en) do
+ category.title.should == "New Category"
+ end
+ I18n.with_locale(:es) do
+ category.title.should == "Mi Nuevo Category"
+ end
+
+ response.should redirect_to(admin_categories_path)
+ end
+ end
+
+ context 'when editing a public body category' do
+ before do
+ @category = FactoryGirl.create(:public_body_category)
+ I18n.with_locale('es') do
+ @category.title = 'Los category'
+ @category.save!
+ end
+ end
+
+ render_views
+
+ it "edits a public body category" do
+ get :edit, :id => @category.id
+ end
+
+ it "edits a public body in another locale" do
+ get :edit, {:id => @category.id, :locale => :en}
+
+ # When editing a body, the controller returns all available translations
+ assigns[:category].find_translation_by_locale("es").title.should == 'Los category'
+ response.should render_template('edit')
+ end
+ end
+
+ context 'when updating a public body category' do
+
+ before do
+ @heading = FactoryGirl.create(:public_body_heading)
+ @category = FactoryGirl.create(:public_body_category)
+ link = FactoryGirl.create(:public_body_category_link,
+ :public_body_category => @category,
+ :public_body_heading => @heading,
+ :category_display_order => 0)
+ @tag = @category.category_tag
+ I18n.with_locale('es') do
+ @category.title = 'Los category'
+ @category.save!
+ end
+ end
+
+ render_views
+
+ it "saves edits to a public body category" do
+ post :update, { :id => @category.id,
+ :public_body_category => { :title => "Renamed" } }
+ request.flash[:notice].should include('successful')
+ pbc = PublicBodyCategory.find(@category.id)
+ pbc.title.should == "Renamed"
+ end
+
+ it "saves edits to a public body category's heading associations" do
+ @category.public_body_headings.should == [@heading]
+ heading = FactoryGirl.create(:public_body_heading)
+ post :update, { :id => @category.id,
+ :public_body_category => { :title => "Renamed" },
+ :headings => {"heading_#{heading.id}" => heading.id} }
+ request.flash[:notice].should include('successful')
+ pbc = PublicBodyCategory.find(@category.id)
+ pbc.public_body_headings.should == [heading]
+ end
+
+ it "saves edits to a public body category in another locale" do
+ I18n.with_locale(:es) do
+ @category.title.should == 'Los category'
+ post :update, {
+ :id => @category.id,
+ :public_body_category => {
+ :title => "Category",
+ :translated_versions => {
+ @category.id => {:locale => "es",
+ :title => "Renamed"}
+ }
+ }
+ }
+ request.flash[:notice].should include('successful')
+ end
+
+ pbc = PublicBodyCategory.find(@category.id)
+ I18n.with_locale(:es) do
+ pbc.title.should == "Renamed"
+ end
+ I18n.with_locale(:en) do
+ pbc.title.should == "Category"
+ end
+ end
+
+ it "does not save edits to category_tag if the category has associated bodies" do
+ body = FactoryGirl.create(:public_body, :tag_string => @tag)
+ post :update, { :id => @category.id,
+ :public_body_category => { :category_tag => "renamed" } }
+ request.flash[:notice].should include('can\'t')
+ pbc = PublicBodyCategory.find(@category.id)
+ pbc.category_tag.should == @tag
+ end
+
+
+ it "save edits to category_tag if the category has no associated bodies" do
+ category = PublicBodyCategory.create(:title => "Empty Category", :category_tag => "empty", :description => "-")
+ post :update, { :id => category.id,
+ :public_body_category => { :category_tag => "renamed" } }
+ request.flash[:notice].should include('success')
+ pbc = PublicBodyCategory.find(category.id)
+ pbc.category_tag.should == "renamed"
+ end
+ end
+
+ context 'when destroying a public body category' do
+
+ it "destroys a public body category" do
+ pbc = PublicBodyCategory.create(:title => "Empty Category", :category_tag => "empty", :description => "-")
+ n = PublicBodyCategory.count
+ post :destroy, { :id => pbc.id }
+ response.should redirect_to(admin_categories_path)
+ PublicBodyCategory.count.should == n - 1
+ end
+ end
+
+
+end
diff --git a/spec/controllers/admin_public_body_headings_controller_spec.rb b/spec/controllers/admin_public_body_headings_controller_spec.rb
new file mode 100644
index 000000000..31517d238
--- /dev/null
+++ b/spec/controllers/admin_public_body_headings_controller_spec.rb
@@ -0,0 +1,240 @@
+require 'spec_helper'
+
+describe AdminPublicBodyHeadingsController do
+
+ context 'when showing the form for a new public body category' do
+ it 'should assign a new public body heading to the view' do
+ get :new
+ assigns[:heading].should be_a(PublicBodyHeading)
+ end
+ end
+
+ context 'when creating a public body heading' do
+ it "creates a new public body heading in one locale" do
+ n = PublicBodyHeading.count
+ post :create, {
+ :public_body_heading => {
+ :name => 'New Heading'
+ }
+ }
+ PublicBodyHeading.count.should == n + 1
+
+ heading = PublicBodyHeading.find_by_name("New Heading")
+ response.should redirect_to(admin_categories_path)
+ end
+
+ it 'creates a new public body heading with multiple locales' do
+ n = PublicBodyHeading.count
+ post :create, {
+ :public_body_heading => {
+ :name => 'New Heading',
+ :translated_versions => [{ :locale => "es",
+ :name => "Mi Nuevo Heading" }]
+ }
+ }
+ PublicBodyHeading.count.should == n + 1
+
+ heading = PublicBodyHeading.find_by_name("New Heading")
+ heading.translations.map {|t| t.locale.to_s}.sort.should == ["en", "es"]
+ I18n.with_locale(:en) do
+ heading.name.should == "New Heading"
+ end
+ I18n.with_locale(:es) do
+ heading.name.should == "Mi Nuevo Heading"
+ end
+
+ response.should redirect_to(admin_categories_path)
+ end
+ end
+
+ context 'when editing a public body heading' do
+ before do
+ @heading = FactoryGirl.create(:public_body_heading)
+ end
+
+ render_views
+
+ it "edits a public body heading" do
+ get :edit, :id => @heading.id
+ end
+ end
+
+ context 'when updating a public body heading' do
+ before do
+ @heading = FactoryGirl.create(:public_body_heading)
+ @name = @heading.name
+ end
+
+ it "saves edits to a public body heading" do
+ post :update, { :id => @heading.id,
+ :public_body_heading => { :name => "Renamed" } }
+ request.flash[:notice].should include('successful')
+ found_heading = PublicBodyHeading.find(@heading.id)
+ found_heading.name.should == "Renamed"
+ end
+
+ it "saves edits to a public body heading in another locale" do
+ I18n.with_locale(:es) do
+ post :update, {
+ :id => @heading.id,
+ :public_body_heading => {
+ :name => @name,
+ :translated_versions => {
+ @heading.id => {:locale => "es",
+ :name => "Renamed"}
+ }
+ }
+ }
+ request.flash[:notice].should include('successful')
+ end
+
+ heading = PublicBodyHeading.find(@heading.id)
+ I18n.with_locale(:es) do
+ heading.name.should == "Renamed"
+ end
+ I18n.with_locale(:en) do
+ heading.name.should == @name
+ end
+ end
+ end
+
+ context 'when destroying a public body heading' do
+
+ before do
+ @heading = FactoryGirl.create(:public_body_heading)
+ end
+
+ it "does not destroy a public body heading that has associated categories" do
+ category = FactoryGirl.create(:public_body_category)
+ link = FactoryGirl.create(:public_body_category_link,
+ :public_body_category => category,
+ :public_body_heading => @heading,
+ :category_display_order => 0)
+ n = PublicBodyHeading.count
+ post :destroy, { :id => @heading.id }
+ response.should redirect_to(edit_admin_heading_path(@heading))
+ PublicBodyHeading.count.should == n
+ end
+
+ it "destroys an empty public body heading" do
+ n = PublicBodyHeading.count
+ post :destroy, { :id => @heading.id }
+ response.should redirect_to(admin_categories_path)
+ PublicBodyHeading.count.should == n - 1
+ end
+ end
+
+ context 'when reordering public body headings' do
+
+ render_views
+
+ before do
+ @first = FactoryGirl.create(:public_body_heading, :display_order => 0)
+ @second = FactoryGirl.create(:public_body_heading, :display_order => 1)
+ @default_params = { :headings => [@second.id, @first.id] }
+ end
+
+ def make_request(params=@default_params)
+ post :reorder, params
+ end
+
+ context 'when handling valid input' do
+
+ it 'should reorder headings according to their position in the submitted params' do
+ make_request
+ PublicBodyHeading.find(@second.id).display_order.should == 0
+ PublicBodyHeading.find(@first.id).display_order.should == 1
+ end
+
+ it 'should return a "success" status' do
+ make_request
+ response.should be_success
+ end
+ end
+
+ context 'when handling invalid input' do
+
+ before do
+ @params = { :headings => [@second.id, @first.id, @second.id + 1]}
+ end
+
+ it 'should return an "unprocessable entity" status and an error message' do
+ make_request(@params)
+ assert_response :unprocessable_entity
+ response.body.should match("Couldn't find PublicBodyHeading with id")
+ end
+
+ it 'should not reorder headings' do
+ make_request(@params)
+ PublicBodyHeading.find(@first.id).display_order.should == 0
+ PublicBodyHeading.find(@second.id).display_order.should == 1
+ end
+
+ end
+ end
+
+ context 'when reordering public body categories' do
+
+ render_views
+
+ before do
+ @heading = FactoryGirl.create(:public_body_heading)
+ @first_category = FactoryGirl.create(:public_body_category)
+ @first_link = FactoryGirl.create(:public_body_category_link,
+ :public_body_category => @first_category,
+ :public_body_heading => @heading,
+ :category_display_order => 0)
+ @second_category = FactoryGirl.create(:public_body_category)
+ @second_link = FactoryGirl.create(:public_body_category_link,
+ :public_body_category => @second_category,
+ :public_body_heading => @heading,
+ :category_display_order => 1)
+ @default_params = { :categories => [@second_category.id, @first_category.id],
+ :id => @heading }
+ @old_order = [@first_category, @second_category]
+ @new_order = [@second_category, @first_category]
+ end
+
+ def make_request(params=@default_params)
+ post :reorder_categories, params
+ end
+
+ context 'when handling valid input' do
+
+ it 'should reorder categories for the heading according to their position \
+ in the submitted params' do
+
+ @heading.public_body_categories.should == @old_order
+ make_request
+ @heading.public_body_categories(reload=true).should == @new_order
+ end
+
+ it 'should return a success status' do
+ make_request
+ response.should be_success
+ end
+ end
+
+ context 'when handling invalid input' do
+
+ before do
+ @new_category = FactoryGirl.create(:public_body_category)
+ @params = @default_params.merge(:categories => [@second_category.id,
+ @first_category.id,
+ @new_category.id])
+ end
+
+ it 'should return an "unprocessable entity" status and an error message' do
+ make_request(@params)
+ assert_response :unprocessable_entity
+ response.body.should match("Couldn't find PublicBodyCategoryLink")
+ end
+
+ it 'should not reorder the categories for the heading' do
+ make_request(@params)
+ @heading.public_body_categories(reload=true).should == @old_order
+ end
+ end
+
+ end
+end
diff --git a/spec/controllers/admin_spam_addresses_controller_spec.rb b/spec/controllers/admin_spam_addresses_controller_spec.rb
index da1e9bb5a..a1e434159 100644
--- a/spec/controllers/admin_spam_addresses_controller_spec.rb
+++ b/spec/controllers/admin_spam_addresses_controller_spec.rb
@@ -37,7 +37,7 @@ describe AdminSpamAddressesController do
it 'redirects to the index action if successful' do
SpamAddress.any_instance.stub(:save).and_return(true)
post :create, :spam_address => spam_params
- expect(response).to redirect_to(spam_addresses_path)
+ expect(response).to redirect_to(admin_spam_addresses_path)
end
it 'notifies the admin the spam address has been created' do
@@ -83,7 +83,7 @@ describe AdminSpamAddressesController do
end
it 'redirects to the index action' do
- expect(response).to redirect_to(spam_addresses_path)
+ expect(response).to redirect_to(admin_spam_addresses_path)
end
end
diff --git a/spec/controllers/public_body_controller_spec.rb b/spec/controllers/public_body_controller_spec.rb
index f64975580..fc7143522 100644
--- a/spec/controllers/public_body_controller_spec.rb
+++ b/spec/controllers/public_body_controller_spec.rb
@@ -7,6 +7,7 @@ describe PublicBodyController, "when showing a body" do
render_views
before(:each) do
+ PublicBodyCategory.stub!(:load_categories)
load_raw_emails_data
get_fixtures_xapian_index
end
@@ -75,6 +76,10 @@ end
describe PublicBodyController, "when listing bodies" do
render_views
+ before(:each) do
+ PublicBodyCategory.stub!(:load_categories)
+ end
+
it "should be successful" do
get :list
response.should be_success
@@ -204,16 +209,19 @@ describe PublicBodyController, "when listing bodies" do
end
end
- it "should list a tagged thing on the appropriate list page, and others on the other page, and all still on the all page" do
- load_test_categories
-
- public_bodies(:humpadink_public_body).tag_string = "foo local_council"
+ it "should list a tagged thing on the appropriate list page, and others on the other page,
+ and all still on the all page" do
+ category = FactoryGirl.create(:public_body_category)
+ heading = FactoryGirl.create(:public_body_heading)
+ PublicBodyCategoryLink.create(:public_body_heading_id => heading.id,
+ :public_body_category_id => category.id)
+ public_bodies(:humpadink_public_body).tag_string = category.category_tag
- get :list, :tag => "local_council"
+ get :list, :tag => category.category_tag
response.should render_template('list')
assigns[:public_bodies].should == [ public_bodies(:humpadink_public_body) ]
- assigns[:tag].should == "local_council"
- assigns[:description].should == "in the category ‘Local councils’"
+ assigns[:tag].should == category.category_tag
+ assigns[:description].should == "in the category ‘#{category.title}’"
get :list, :tag => "other"
response.should render_template('list')
diff --git a/spec/controllers/user_controller_spec.rb b/spec/controllers/user_controller_spec.rb
index e4854fe6b..413d395c5 100644
--- a/spec/controllers/user_controller_spec.rb
+++ b/spec/controllers/user_controller_spec.rb
@@ -21,7 +21,8 @@ describe UserController, "when redirecting a show request to a canonical url" do
it 'should not redirect a long canonical name that has a numerical suffix' do
User.stub!(:find).with(:first, anything()).and_return(mock_model(User,
:url_name => 'bob_smithbob_smithbob_smithbob_s_2',
- :name => 'Bob Smith Bob Smith Bob Smith Bob Smith'))
+ :name => 'Bob Smith Bob Smith Bob Smith Bob Smith',
+ :info_requests => []))
User.stub!(:find).with(:all, anything()).and_return([])
get :show, :url_name => 'bob_smithbob_smithbob_smithbob_s_2'
response.should be_success
@@ -107,6 +108,15 @@ describe UserController, "when showing a user" do
]
end
+ it 'filters by the given request status' do
+ get :show, :url_name => 'bob_smith',
+ :user_query => 'money',
+ :request_latest_status => 'waiting_response'
+ assigns[:xapian_requests].results.map{|x|x[:model].info_request}.should =~ [
+ info_requests(:naughty_chicken_request)
+ ]
+ end
+
it "should not show unconfirmed users" do
begin
get :show, :url_name => "unconfirmed_user"
diff --git a/spec/factories/public_body_categories.rb b/spec/factories/public_body_categories.rb
new file mode 100644
index 000000000..baa474c6b
--- /dev/null
+++ b/spec/factories/public_body_categories.rb
@@ -0,0 +1,8 @@
+
+FactoryGirl.define do
+ factory :public_body_category do
+ sequence(:title) { |n| "Example Public Body Category #{n}" }
+ sequence(:category_tag) { |n| "example_tag_#{n}" }
+ sequence(:description) { |n| "Example Public body Description #{n}" }
+ end
+end
diff --git a/spec/factories/public_body_category_links.rb b/spec/factories/public_body_category_links.rb
new file mode 100644
index 000000000..7663b1f52
--- /dev/null
+++ b/spec/factories/public_body_category_links.rb
@@ -0,0 +1,6 @@
+FactoryGirl.define do
+ factory :public_body_category_link do
+ association :public_body_category
+ association :public_body_heading
+ end
+end
diff --git a/spec/factories/public_body_headings.rb b/spec/factories/public_body_headings.rb
new file mode 100644
index 000000000..ed54ddada
--- /dev/null
+++ b/spec/factories/public_body_headings.rb
@@ -0,0 +1,5 @@
+FactoryGirl.define do
+ factory :public_body_heading do
+ sequence(:name) { |n| "Example Public Body Heading #{n}" }
+ end
+end
diff --git a/spec/fixtures/info_request_events.yml b/spec/fixtures/info_request_events.yml
index b2f40cc37..23ef80cc2 100644
--- a/spec/fixtures/info_request_events.yml
+++ b/spec/fixtures/info_request_events.yml
@@ -31,8 +31,10 @@ silly_outgoing_message_event:
info_request_id: 103
event_type: sent
created_at: 2007-10-14 10:41:12.686264
- described_state:
outgoing_message_id: 2
+ calculated_state: waiting_response
+ described_state: waiting_response
+ last_described_at: 2007-10-14 10:41:12.686264
useless_incoming_message_event:
id: 902
params_yaml: "--- \n\
diff --git a/spec/lib/public_body_categories_spec.rb b/spec/lib/public_body_categories_spec.rb
deleted file mode 100644
index e53d9a028..000000000
--- a/spec/lib/public_body_categories_spec.rb
+++ /dev/null
@@ -1,42 +0,0 @@
-require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
-
-describe PublicBodyCategories do
-
- before do
- load_test_categories
- end
-
- describe 'when asked for categories with headings' do
-
- it 'should return a list of headings as plain strings, each followed by n tag specifications as
- lists in the form:
- ["tag_to_use_as_category", "Sub category title", "Instance description"]' do
- expected_categories = ["Local and regional", ["local_council",
- "Local councils",
- "a local council"],
- "Miscellaneous", ["other",
- "Miscellaneous",
- "miscellaneous"]]
- PublicBodyCategories::get().with_headings().should == expected_categories
- end
-
- end
-
- describe 'when asked for headings' do
-
- it 'should return a list of headings' do
- PublicBodyCategories::get().headings().should == ['Local and regional', 'Miscellaneous']
- end
-
- end
-
- describe 'when asked for tags by headings' do
-
- it 'should return a hash of tags keyed by heading' do
- PublicBodyCategories::get().by_heading().should == {'Local and regional' => ['local_council'],
- 'Miscellaneous' => ['other']}
- end
-
- end
-
-end \ No newline at end of file
diff --git a/spec/models/public_body_category/category_collection_spec.rb b/spec/models/public_body_category/category_collection_spec.rb
new file mode 100644
index 000000000..1fbcbe739
--- /dev/null
+++ b/spec/models/public_body_category/category_collection_spec.rb
@@ -0,0 +1,81 @@
+require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper')
+
+describe PublicBodyCategory::CategoryCollection do
+ context "requesting data" do
+
+ before do
+ data = [ "Local and regional",
+ [ "local_council", "Local councils", "a local council" ],
+ "Miscellaneous",
+ [ "other", "Miscellaneous", "miscellaneous" ] ]
+ @categories = PublicBodyCategory::CategoryCollection.new
+ data.each { |item| @categories << item }
+ end
+
+ describe 'when asked for headings' do
+
+ it 'should return a list of headings' do
+ @categories.headings().should == ['Local and regional', 'Miscellaneous']
+ end
+
+ end
+
+ describe 'when asked for categories with headings' do
+ it 'should return a list of headings as plain strings, each followed by n tag specifications as
+ lists in the form:
+ ["tag_to_use_as_category", "Sub category title", "Instance description"]' do
+ expected_categories = ["Local and regional", ["local_council",
+ "Local councils",
+ "a local council"],
+ "Miscellaneous", ["other",
+ "Miscellaneous",
+ "miscellaneous"]]
+ @categories.with_headings().should == expected_categories
+ end
+ end
+
+
+
+ describe 'when asked for tags by headings' do
+ it 'should return a hash of tags keyed by heading' do
+ @categories.by_heading().should == {'Local and regional' => ['local_council'],
+ 'Miscellaneous' => ['other']}
+ end
+ end
+
+ describe 'when asked for categories with description' do
+ it 'should return a list of tag specifications as lists in the form:
+ ["tag_to_use_as_category", "Sub category title", "Instance description"]' do
+ expected_categories = [
+ ["local_council", "Local councils", "a local council"],
+ ["other", "Miscellaneous", "miscellaneous"]
+ ]
+ @categories.with_description().should == expected_categories
+ end
+ end
+
+ describe 'when asked for tags' do
+ it 'should return a list of tags' do
+ @categories.tags().should == ["local_council", "other"]
+ end
+ end
+
+ describe 'when asked for categories by tag' do
+ it 'should return a hash of categories keyed by tag' do
+ @categories.by_tag().should == {
+ "local_council" => "Local councils",
+ "other" => "Miscellaneous"
+ }
+ end
+ end
+
+ describe 'when asked for singular_by_tag' do
+ it 'should return a hash of category descriptions keyed by tag' do
+ @categories.singular_by_tag().should == {
+ "local_council" => "a local council",
+ "other" => "miscellaneous"
+ }
+ end
+ end
+ end
+end
diff --git a/spec/models/public_body_category_link_spec.rb b/spec/models/public_body_category_link_spec.rb
new file mode 100644
index 000000000..8d91f02d5
--- /dev/null
+++ b/spec/models/public_body_category_link_spec.rb
@@ -0,0 +1,53 @@
+# == Schema Information
+#
+# Table name: public_body_category_link
+#
+# public_body_category_id :integer not null
+# public_body_heading_id :integer not null
+# category_display_order :integer
+#
+
+require 'spec_helper'
+
+describe PublicBodyHeading, 'when validating' do
+
+ it 'should set a default display order based on the next available display order' do
+ heading = FactoryGirl.create(:public_body_heading)
+ category = FactoryGirl.create(:public_body_category)
+ category_link = PublicBodyCategoryLink.new(:public_body_heading => heading,
+ :public_body_category => category)
+ category_link.valid?
+ category_link.category_display_order.should == PublicBodyCategoryLink.next_display_order(heading)
+ end
+
+ it 'should be invalid without a category' do
+ category_link = PublicBodyCategoryLink.new
+ category_link.should_not be_valid
+ category_link.errors[:public_body_category].should == ["can't be blank"]
+ end
+
+ it 'should be invalid without a heading' do
+ category_link = PublicBodyCategoryLink.new
+ category_link.should_not be_valid
+ category_link.errors[:public_body_heading].should == ["can't be blank"]
+ end
+
+end
+
+describe PublicBodyCategoryLink, 'when setting a category display order' do
+
+ it 'should return 0 if there are no public body headings' do
+ heading = FactoryGirl.create(:public_body_heading)
+ PublicBodyCategoryLink.next_display_order(heading).should == 0
+ end
+
+ it 'should return one more than the highest display order if there are public body headings' do
+ heading = FactoryGirl.create(:public_body_heading)
+ category = FactoryGirl.create(:public_body_category)
+ category_link = PublicBodyCategoryLink.create(:public_body_heading_id => heading.id,
+ :public_body_category_id => category.id)
+
+ PublicBodyCategoryLink.next_display_order(heading).should == 1
+ end
+
+end
diff --git a/spec/models/public_body_category_spec.rb b/spec/models/public_body_category_spec.rb
new file mode 100644
index 000000000..2d39a7376
--- /dev/null
+++ b/spec/models/public_body_category_spec.rb
@@ -0,0 +1,65 @@
+# == Schema Information
+#
+# Table name: public_body_categories
+#
+# id :integer not null, primary key
+# locale :string
+# title :text not null
+# category_tag :text not null
+# description :text not null
+# display_order :integer
+#
+
+require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
+
+describe PublicBodyCategory do
+ describe 'when loading the data' do
+ it 'should use the display_order field to preserve the original data order' do
+ PublicBodyCategory.add(:en, [
+ "Local and regional",
+ [ "local_council", "Local councils", "a local council" ],
+ "Miscellaneous",
+ [ "other", "Miscellaneous", "miscellaneous" ],
+ [ "aardvark", "Aardvark", "daft test"],])
+
+ headings = PublicBodyHeading.all
+ cat_group1 = headings[0].public_body_categories
+ cat_group1.count.should eq 1
+ cat_group1[0].title.should eq "Local councils"
+
+ cat_group2 = headings[1].public_body_categories
+ cat_group2.count.should eq 2
+ cat_group2[0].title.should eq "Miscellaneous"
+ cat_group2[0].public_body_category_links.where(
+ :public_body_heading_id => headings[1].id).
+ first.
+ category_display_order.should eq 0
+
+ cat_group2[1].title.should eq "Aardvark"
+ cat_group2[1].public_body_category_links.where(
+ :public_body_heading_id => headings[1].id).
+ first.
+ category_display_order.should eq 1
+ end
+ end
+
+ context 'when validating' do
+
+ it 'should require a title' do
+ category = PublicBodyCategory.new
+ category.should_not be_valid
+ category.errors[:title].should == ["Title can't be blank"]
+ end
+
+ it 'should require a category tag' do
+ category = PublicBodyCategory.new
+ category.should_not be_valid
+ category.errors[:category_tag].should == ["Tag can't be blank"]
+ end
+
+ it 'should require a unique tag' do
+ existing = FactoryGirl.create(:public_body_category)
+ PublicBodyCategory.new(:email => existing.category_tag).should_not be_valid
+ end
+ end
+end
diff --git a/spec/models/public_body_heading_spec.rb b/spec/models/public_body_heading_spec.rb
new file mode 100644
index 000000000..add2cac60
--- /dev/null
+++ b/spec/models/public_body_heading_spec.rb
@@ -0,0 +1,68 @@
+# == Schema Information
+#
+# Table name: public_body_headings
+#
+# id :integer not null, primary key
+# locale :string
+# name :text not null
+# display_order :integer
+#
+
+require 'spec_helper'
+
+describe PublicBodyHeading do
+
+ context 'when loading the data' do
+
+ before do
+ PublicBodyCategory.add(:en, [
+ "Local and regional",
+ [ "local_council", "Local councils", "a local council" ],
+ "Miscellaneous",
+ [ "other", "Miscellaneous", "miscellaneous" ],])
+ end
+
+ it 'should use the display_order field to preserve the original data order' do
+ headings = PublicBodyHeading.all
+ headings[0].name.should eq 'Local and regional'
+ headings[0].display_order.should eq 0
+ headings[1].name.should eq 'Miscellaneous'
+ headings[1].display_order.should eq 1
+ end
+
+ end
+
+ context 'when validating' do
+
+ it 'should require a name' do
+ heading = PublicBodyHeading.new
+ heading.should_not be_valid
+ heading.errors[:name].should == ["Name can't be blank"]
+ end
+
+ it 'should require a unique name' do
+ heading = FactoryGirl.create(:public_body_heading)
+ new_heading = PublicBodyHeading.new(:name => heading.name)
+ new_heading.should_not be_valid
+ new_heading.errors[:name].should == ["Name is already taken"]
+ end
+
+ it 'should set a default display order based on the next available display order' do
+ heading = PublicBodyHeading.new
+ heading.valid?
+ heading.display_order.should == PublicBodyHeading.next_display_order
+ end
+ end
+
+ context 'when setting a display order' do
+
+ it 'should return 0 if there are no public body headings' do
+ PublicBodyHeading.next_display_order.should == 0
+ end
+
+ it 'should return one more than the highest display order if there are public body headings' do
+ heading = FactoryGirl.create(:public_body_heading)
+ PublicBodyHeading.next_display_order.should == 1
+ end
+ end
+end
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 0e3fe35c7..74a4891c2 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -201,14 +201,6 @@ Spork.prefork do
I18n.default_locale = original_default_locale
end
- def load_test_categories
- PublicBodyCategories.add(:en, [
- "Local and regional",
- [ "local_council", "Local councils", "a local council" ],
- "Miscellaneous",
- [ "other", "Miscellaneous", "miscellaneous" ],])
- end
-
def basic_auth_login(request, username = nil, password = nil)
username = AlaveteliConfiguration::admin_username if username.nil?
password = AlaveteliConfiguration::admin_password if password.nil?