govuk_content_models 6.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.ruby-version +1 -0
- data/.travis.yml +14 -0
- data/CONTRIBUTING.md +22 -0
- data/Gemfile +4 -0
- data/LICENSE +20 -0
- data/README.md +5 -0
- data/Rakefile +46 -0
- data/app/models/action.rb +60 -0
- data/app/models/answer_edition.rb +13 -0
- data/app/models/artefact.rb +341 -0
- data/app/models/artefact_action.rb +27 -0
- data/app/models/artefact_external_link.rb +15 -0
- data/app/models/business_support/business_size.rb +14 -0
- data/app/models/business_support/business_type.rb +14 -0
- data/app/models/business_support/location.rb +14 -0
- data/app/models/business_support/purpose.rb +14 -0
- data/app/models/business_support/sector.rb +14 -0
- data/app/models/business_support/stage.rb +14 -0
- data/app/models/business_support/support_type.rb +14 -0
- data/app/models/business_support_edition.rb +69 -0
- data/app/models/campaign_edition.rb +72 -0
- data/app/models/completed_transaction_edition.rb +14 -0
- data/app/models/curated_list.rb +32 -0
- data/app/models/edition.rb +286 -0
- data/app/models/expectant.rb +21 -0
- data/app/models/expectation.rb +12 -0
- data/app/models/guide_edition.rb +19 -0
- data/app/models/help_page_edition.rb +13 -0
- data/app/models/licence_edition.rb +35 -0
- data/app/models/local_authority.rb +58 -0
- data/app/models/local_interaction.rb +20 -0
- data/app/models/local_service.rb +49 -0
- data/app/models/local_transaction_edition.rb +49 -0
- data/app/models/overview_dashboard.rb +25 -0
- data/app/models/part.rb +28 -0
- data/app/models/parted.rb +32 -0
- data/app/models/place_edition.rb +20 -0
- data/app/models/programme_edition.rb +26 -0
- data/app/models/simple_smart_answer_edition.rb +66 -0
- data/app/models/simple_smart_answer_edition/node.rb +40 -0
- data/app/models/simple_smart_answer_edition/node/option.rb +31 -0
- data/app/models/tag.rb +88 -0
- data/app/models/transaction_edition.rb +28 -0
- data/app/models/travel_advice_edition.rb +177 -0
- data/app/models/user.rb +54 -0
- data/app/models/video_edition.rb +24 -0
- data/app/models/workflow.rb +217 -0
- data/app/models/workflow_actor.rb +141 -0
- data/app/traits/attachable.rb +60 -0
- data/app/traits/govspeak_smart_quotes_fixer.rb +19 -0
- data/app/traits/taggable.rb +113 -0
- data/app/validators/safe_html.rb +33 -0
- data/app/validators/slug_validator.rb +53 -0
- data/config/mongoid.yml +5 -0
- data/govuk_content_models.gemspec +42 -0
- data/jenkins.sh +7 -0
- data/lib/fact_check_address.rb +36 -0
- data/lib/govuk_content_models.rb +12 -0
- data/lib/govuk_content_models/require_all.rb +14 -0
- data/lib/govuk_content_models/test_helpers/factories.rb +213 -0
- data/lib/govuk_content_models/test_helpers/local_services.rb +24 -0
- data/lib/govuk_content_models/version.rb +4 -0
- data/test/fixtures/contactotron_api_response.json +1 -0
- data/test/fixtures/uploads/image.jpg +0 -0
- data/test/models/artefact_action_test.rb +123 -0
- data/test/models/artefact_external_link_test.rb +32 -0
- data/test/models/artefact_tag_test.rb +52 -0
- data/test/models/artefact_test.rb +583 -0
- data/test/models/business_support/business_size_test.rb +25 -0
- data/test/models/business_support/business_type_test.rb +25 -0
- data/test/models/business_support/location_test.rb +25 -0
- data/test/models/business_support/purpose_test.rb +29 -0
- data/test/models/business_support/sector_test.rb +25 -0
- data/test/models/business_support/stage_test.rb +25 -0
- data/test/models/business_support/support_type_test.rb +25 -0
- data/test/models/business_support_edition_test.rb +186 -0
- data/test/models/campaign_edition_test.rb +90 -0
- data/test/models/curated_list_test.rb +32 -0
- data/test/models/edition_test.rb +826 -0
- data/test/models/fact_check_address_test.rb +36 -0
- data/test/models/help_page_edition_test.rb +38 -0
- data/test/models/licence_edition_test.rb +104 -0
- data/test/models/local_authority_test.rb +113 -0
- data/test/models/local_service_test.rb +199 -0
- data/test/models/local_transaction_edition_test.rb +78 -0
- data/test/models/overview_dashboard_test.rb +47 -0
- data/test/models/simple_smart_answer_edition_test.rb +169 -0
- data/test/models/simple_smart_answer_node_test.rb +134 -0
- data/test/models/simple_smart_answer_option_test.rb +90 -0
- data/test/models/tag_test.rb +92 -0
- data/test/models/time_zone_test.rb +48 -0
- data/test/models/transaction_edition_test.rb +20 -0
- data/test/models/travel_advice_edition_test.rb +480 -0
- data/test/models/user_test.rb +114 -0
- data/test/models/video_edition_test.rb +64 -0
- data/test/models/workflow_actor_test.rb +61 -0
- data/test/models/workflow_test.rb +307 -0
- data/test/test_helper.rb +47 -0
- data/test/traits/attachable_test.rb +143 -0
- data/test/traits/taggable_test.rb +114 -0
- data/test/validators/safe_html_validator_test.rb +86 -0
- data/test/validators/slug_validator_test.rb +42 -0
- metadata +511 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require "safe_html"
|
|
2
|
+
|
|
3
|
+
class ArtefactAction
|
|
4
|
+
include Mongoid::Document
|
|
5
|
+
include Mongoid::Timestamps::Created
|
|
6
|
+
|
|
7
|
+
field "action_type", type: String
|
|
8
|
+
field "snapshot", type: Hash
|
|
9
|
+
|
|
10
|
+
GOVSPEAK_FIELDS = []
|
|
11
|
+
|
|
12
|
+
embedded_in :artefact
|
|
13
|
+
|
|
14
|
+
validates_with SafeHtml
|
|
15
|
+
|
|
16
|
+
# Ideally we would like to use the UID field here, since that will be the
|
|
17
|
+
# same across all applications, but Mongoid doesn't yet support using a
|
|
18
|
+
# custom primary key on a related field
|
|
19
|
+
belongs_to :user
|
|
20
|
+
|
|
21
|
+
# Not validating presence of a user just yet, since there may be some
|
|
22
|
+
# circumstances where we can't reliably determine the user. As an example
|
|
23
|
+
# of this, requests made through the API are not yet tied to a user. If we
|
|
24
|
+
# find out that there are no such circumstances in practice, we can add a
|
|
25
|
+
# validator for :user.
|
|
26
|
+
validates_presence_of :action_type, :snapshot
|
|
27
|
+
end
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class ArtefactExternalLink
|
|
2
|
+
include Mongoid::Document
|
|
3
|
+
|
|
4
|
+
field "title", type: String
|
|
5
|
+
field "url", type: String
|
|
6
|
+
|
|
7
|
+
GOVSPEAK_FIELDS = []
|
|
8
|
+
|
|
9
|
+
embedded_in :artefact
|
|
10
|
+
|
|
11
|
+
validates_with SafeHtml
|
|
12
|
+
|
|
13
|
+
validates_presence_of :title
|
|
14
|
+
validates :url, :presence => true, :format => { :with => URI::regexp(%w{http https}) }
|
|
15
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module BusinessSupport
|
|
2
|
+
class BusinessSize
|
|
3
|
+
include Mongoid::Document
|
|
4
|
+
|
|
5
|
+
field :name, type: String
|
|
6
|
+
field :slug, type: String
|
|
7
|
+
index :slug => 1
|
|
8
|
+
|
|
9
|
+
validates_presence_of :name
|
|
10
|
+
validates_uniqueness_of :name
|
|
11
|
+
validates_presence_of :slug
|
|
12
|
+
validates_uniqueness_of :slug
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module BusinessSupport
|
|
2
|
+
class BusinessType
|
|
3
|
+
include Mongoid::Document
|
|
4
|
+
|
|
5
|
+
field :name, type: String
|
|
6
|
+
field :slug, type: String
|
|
7
|
+
index :slug => 1
|
|
8
|
+
|
|
9
|
+
validates_presence_of :name
|
|
10
|
+
validates_uniqueness_of :name
|
|
11
|
+
validates_presence_of :slug
|
|
12
|
+
validates_uniqueness_of :slug
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module BusinessSupport
|
|
2
|
+
class Location
|
|
3
|
+
include Mongoid::Document
|
|
4
|
+
|
|
5
|
+
field :name, type: String
|
|
6
|
+
field :slug, type: String
|
|
7
|
+
index :slug => 1
|
|
8
|
+
|
|
9
|
+
validates_presence_of :name
|
|
10
|
+
validates_uniqueness_of :name
|
|
11
|
+
validates_presence_of :slug
|
|
12
|
+
validates_uniqueness_of :slug
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module BusinessSupport
|
|
2
|
+
class Purpose
|
|
3
|
+
include Mongoid::Document
|
|
4
|
+
|
|
5
|
+
field :name, type: String
|
|
6
|
+
field :slug, type: String
|
|
7
|
+
index :slug => 1
|
|
8
|
+
|
|
9
|
+
validates_presence_of :name
|
|
10
|
+
validates_uniqueness_of :name
|
|
11
|
+
validates_presence_of :slug
|
|
12
|
+
validates_uniqueness_of :slug
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module BusinessSupport
|
|
2
|
+
class Sector
|
|
3
|
+
include Mongoid::Document
|
|
4
|
+
|
|
5
|
+
field :name, type: String
|
|
6
|
+
field :slug, type: String
|
|
7
|
+
index :slug => 1
|
|
8
|
+
|
|
9
|
+
validates_presence_of :name
|
|
10
|
+
validates_uniqueness_of :name
|
|
11
|
+
validates_presence_of :slug
|
|
12
|
+
validates_uniqueness_of :slug
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module BusinessSupport
|
|
2
|
+
class Stage
|
|
3
|
+
include Mongoid::Document
|
|
4
|
+
|
|
5
|
+
field :name, type: String
|
|
6
|
+
field :slug, type: String
|
|
7
|
+
index :slug => 1
|
|
8
|
+
|
|
9
|
+
validates_presence_of :name
|
|
10
|
+
validates_uniqueness_of :name
|
|
11
|
+
validates_presence_of :slug
|
|
12
|
+
validates_uniqueness_of :slug
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module BusinessSupport
|
|
2
|
+
class SupportType
|
|
3
|
+
include Mongoid::Document
|
|
4
|
+
|
|
5
|
+
field :name, type: String
|
|
6
|
+
field :slug, type: String
|
|
7
|
+
index :slug => 1
|
|
8
|
+
|
|
9
|
+
validates_presence_of :name
|
|
10
|
+
validates_uniqueness_of :name
|
|
11
|
+
validates_presence_of :slug
|
|
12
|
+
validates_uniqueness_of :slug
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require "edition"
|
|
3
|
+
|
|
4
|
+
class BusinessSupportEdition < Edition
|
|
5
|
+
|
|
6
|
+
include Mongoid::MultiParameterAttributes
|
|
7
|
+
|
|
8
|
+
field :short_description, type: String
|
|
9
|
+
field :body, type: String
|
|
10
|
+
field :min_value, type: Integer
|
|
11
|
+
field :max_value, type: Integer
|
|
12
|
+
field :max_employees, type: Integer
|
|
13
|
+
field :organiser, type: String
|
|
14
|
+
field :eligibility, type: String
|
|
15
|
+
field :evaluation, type: String
|
|
16
|
+
field :additional_information, type: String
|
|
17
|
+
field :continuation_link, type: String
|
|
18
|
+
field :will_continue_on, type: String
|
|
19
|
+
field :contact_details, type: String
|
|
20
|
+
field :business_support_identifier, type: String
|
|
21
|
+
|
|
22
|
+
field :priority, type: Integer, default: 1
|
|
23
|
+
field :business_types, type: Array, default: []
|
|
24
|
+
field :business_sizes, type: Array, default: []
|
|
25
|
+
field :locations, type: Array, default: []
|
|
26
|
+
field :purposes, type: Array, default: []
|
|
27
|
+
field :sectors, type: Array, default: []
|
|
28
|
+
field :stages, type: Array, default: []
|
|
29
|
+
field :support_types, type: Array, default: []
|
|
30
|
+
field :start_date, type: Date
|
|
31
|
+
field :end_date, type: Date
|
|
32
|
+
|
|
33
|
+
index :business_support_identifier
|
|
34
|
+
|
|
35
|
+
GOVSPEAK_FIELDS = Edition::GOVSPEAK_FIELDS + [:body, :eligibility, :evaluation, :additional_information]
|
|
36
|
+
|
|
37
|
+
validate :min_must_be_less_than_max
|
|
38
|
+
validates :business_support_identifier, :presence => true
|
|
39
|
+
validate :business_support_identifier_unique
|
|
40
|
+
validates_format_of :continuation_link, :with => URI::regexp(%w(http https)), :allow_blank => true
|
|
41
|
+
|
|
42
|
+
# https://github.com/mongoid/mongoid/issues/1735 Really Mongoid‽
|
|
43
|
+
validates :min_value, :max_value, :max_employees, :numericality => {:allow_nil => true, :only_integer => true}
|
|
44
|
+
|
|
45
|
+
@fields_to_clone = [:body, :min_value, :max_value, :max_employees, :organiser,
|
|
46
|
+
:eligibility, :evaluation, :additional_information, :continuation_link,
|
|
47
|
+
:will_continue_on, :contact_details, :short_description,
|
|
48
|
+
:business_support_identifier]
|
|
49
|
+
|
|
50
|
+
def whole_body
|
|
51
|
+
[short_description, body].join("\n\n")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def min_must_be_less_than_max
|
|
57
|
+
if !min_value.nil? && !max_value.nil? && min_value > max_value
|
|
58
|
+
errors[:min_value] << "Min value must be smaller than max value"
|
|
59
|
+
errors[:max_value] << "Max value must be larger than min value"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def business_support_identifier_unique
|
|
64
|
+
if self.class.without_state('archived').where(:business_support_identifier => business_support_identifier,
|
|
65
|
+
:panopticon_id.ne => panopticon_id).any?
|
|
66
|
+
errors.add(:business_support_identifier, :taken)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'attachable'
|
|
2
|
+
require 'edition'
|
|
3
|
+
|
|
4
|
+
class CampaignEdition < Edition
|
|
5
|
+
include Attachable
|
|
6
|
+
|
|
7
|
+
field :body, type: String
|
|
8
|
+
field :organisation_formatted_name, type: String
|
|
9
|
+
field :organisation_url, type: String
|
|
10
|
+
field :organisation_brand_colour, type: String
|
|
11
|
+
field :organisation_crest, type: String
|
|
12
|
+
|
|
13
|
+
attaches :large_image, :medium_image, :small_image
|
|
14
|
+
|
|
15
|
+
GOVSPEAK_FIELDS = Edition::GOVSPEAK_FIELDS + [:body]
|
|
16
|
+
@fields_to_clone = [
|
|
17
|
+
:body, :large_image_id, :medium_image_id, :small_image_id,
|
|
18
|
+
:organisation_formatted_name, :organisation_url, :organisation_brand_colour, :organisation_crest
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
BRAND_COLOURS = [
|
|
22
|
+
"attorney-generals-office",
|
|
23
|
+
"cabinet-office",
|
|
24
|
+
"department-for-business-innovation-skills",
|
|
25
|
+
"department-for-communities-and-local-government",
|
|
26
|
+
"department-for-culture-media-sport",
|
|
27
|
+
"department-for-education",
|
|
28
|
+
"department-for-environment-food-rural-affairs",
|
|
29
|
+
"department-for-international-development",
|
|
30
|
+
"department-for-transport",
|
|
31
|
+
"department-for-work-pensions",
|
|
32
|
+
"department-of-energy-climate-change",
|
|
33
|
+
"department-of-health",
|
|
34
|
+
"foreign-commonwealth-office",
|
|
35
|
+
"hm-government",
|
|
36
|
+
"hm-revenue-customs",
|
|
37
|
+
"hm-treasury",
|
|
38
|
+
"home-office",
|
|
39
|
+
"ministry-of-defence",
|
|
40
|
+
"ministry-of-justice",
|
|
41
|
+
"northern-ireland-office",
|
|
42
|
+
"office-of-the-advocate-general-for-scotland",
|
|
43
|
+
"office-of-the-leader-of-the-house-of-lords",
|
|
44
|
+
"scotland-office",
|
|
45
|
+
"the-office-of-the-leader-of-the-house-of-commons",
|
|
46
|
+
"uk-export-finance",
|
|
47
|
+
"uk-trade-investment",
|
|
48
|
+
"wales-office"
|
|
49
|
+
]
|
|
50
|
+
CRESTS = {
|
|
51
|
+
"No identity" => "no-identity",
|
|
52
|
+
"Single identity" => "single-identity",
|
|
53
|
+
"Department for Business, Innovation and Skills" => "bis",
|
|
54
|
+
"Scotland Office" => "so",
|
|
55
|
+
"Home Office" => "ho",
|
|
56
|
+
"Ministry of Defence" => "mod",
|
|
57
|
+
"Wales Office" => "wales",
|
|
58
|
+
"HM Coastguard" => "coastguard",
|
|
59
|
+
"Portcullis" => "portcullis",
|
|
60
|
+
"UK Hydrographic Office" => "ukho",
|
|
61
|
+
"Executive Office" => "eo",
|
|
62
|
+
"HM Revenue and Customs" => "hmrc",
|
|
63
|
+
"UK Atomic Energy Authority" => "ukaea"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
validates :organisation_brand_colour, :inclusion => { :in => BRAND_COLOURS, :allow_blank => true }
|
|
67
|
+
validates :organisation_crest, :inclusion => { :in => CRESTS.values, :allow_blank => true }
|
|
68
|
+
|
|
69
|
+
def whole_body
|
|
70
|
+
self.body
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require "slug_validator"
|
|
2
|
+
require "traits/taggable"
|
|
3
|
+
require "safe_html"
|
|
4
|
+
|
|
5
|
+
class CuratedList
|
|
6
|
+
include Mongoid::Document
|
|
7
|
+
include Mongoid::Timestamps
|
|
8
|
+
|
|
9
|
+
include Taggable
|
|
10
|
+
stores_tags_for :sections
|
|
11
|
+
|
|
12
|
+
field "slug", type: String
|
|
13
|
+
has_and_belongs_to_many :artefacts, class_name: "Artefact"
|
|
14
|
+
|
|
15
|
+
index "slug"
|
|
16
|
+
|
|
17
|
+
GOVSPEAK_FIELDS = []
|
|
18
|
+
|
|
19
|
+
validates :slug, presence: true, uniqueness: true, slug: true
|
|
20
|
+
validates_with SafeHtml
|
|
21
|
+
|
|
22
|
+
def self.find_by_slug(slug)
|
|
23
|
+
where(slug: slug).first
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Returns the artefacts in order, skipping missing artefacts
|
|
27
|
+
def artefacts
|
|
28
|
+
Artefact.where(:_id.in => artefact_ids).sort_by do |artefact|
|
|
29
|
+
artefact_ids.index(artefact.id)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
require "workflow"
|
|
2
|
+
require "fact_check_address"
|
|
3
|
+
|
|
4
|
+
class Edition
|
|
5
|
+
include Mongoid::Document
|
|
6
|
+
include Mongoid::Timestamps
|
|
7
|
+
include Workflow
|
|
8
|
+
|
|
9
|
+
field :panopticon_id, type: String
|
|
10
|
+
field :version_number, type: Integer, default: 1
|
|
11
|
+
field :sibling_in_progress, type: Integer, default: nil
|
|
12
|
+
field :business_proposition, type: Boolean, default: false
|
|
13
|
+
|
|
14
|
+
field :title, type: String
|
|
15
|
+
field :created_at, type: DateTime, default: lambda { Time.zone.now }
|
|
16
|
+
field :overview, type: String
|
|
17
|
+
field :alternative_title, type: String
|
|
18
|
+
field :slug, type: String
|
|
19
|
+
field :section, type: String
|
|
20
|
+
field :department, type: String
|
|
21
|
+
field :rejected_count, type: Integer, default: 0
|
|
22
|
+
field :tags, type: String
|
|
23
|
+
|
|
24
|
+
field :assignee, type: String
|
|
25
|
+
field :creator, type: String
|
|
26
|
+
field :publisher, type: String
|
|
27
|
+
field :archiver, type: String
|
|
28
|
+
|
|
29
|
+
GOVSPEAK_FIELDS = []
|
|
30
|
+
|
|
31
|
+
belongs_to :assigned_to, class_name: "User"
|
|
32
|
+
|
|
33
|
+
scope :lined_up, where(state: "lined_up")
|
|
34
|
+
scope :draft, where(state: "draft")
|
|
35
|
+
scope :amends_needed, where(state: "amends_needed")
|
|
36
|
+
scope :in_review, where(state: "in_review")
|
|
37
|
+
scope :fact_check, where(state: "fact_check")
|
|
38
|
+
scope :fact_check_received, where(state: "fact_check_received")
|
|
39
|
+
scope :ready, where(state: "ready")
|
|
40
|
+
scope :published, where(state: "published")
|
|
41
|
+
scope :archived, where(state: "archived")
|
|
42
|
+
scope :in_progress, where(:state.nin => ["archived", "published"])
|
|
43
|
+
scope :assigned_to, lambda { |user|
|
|
44
|
+
if user
|
|
45
|
+
where(assigned_to_id: user.id)
|
|
46
|
+
else
|
|
47
|
+
where(:assigned_to_id.exists => false)
|
|
48
|
+
end
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
validates :title, presence: true
|
|
52
|
+
validates :version_number, presence: true
|
|
53
|
+
validates :panopticon_id, presence: true
|
|
54
|
+
validates_with SafeHtml
|
|
55
|
+
|
|
56
|
+
before_save :check_for_archived_artefact
|
|
57
|
+
before_destroy :destroy_artefact
|
|
58
|
+
|
|
59
|
+
index "assigned_to_id"
|
|
60
|
+
index "panopticon_id"
|
|
61
|
+
index "state"
|
|
62
|
+
|
|
63
|
+
class << self; attr_accessor :fields_to_clone end
|
|
64
|
+
@fields_to_clone = []
|
|
65
|
+
|
|
66
|
+
alias_method :admin_list_title, :title
|
|
67
|
+
|
|
68
|
+
def series
|
|
69
|
+
Edition.where(panopticon_id: panopticon_id)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def history
|
|
73
|
+
series.order([:version_number, :desc])
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def siblings
|
|
77
|
+
series.excludes(id: id)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def previous_siblings
|
|
81
|
+
siblings.where(:version_number.lt => version_number)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def subsequent_siblings
|
|
85
|
+
siblings.where(:version_number.gt => version_number)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def latest_edition?
|
|
89
|
+
subsequent_siblings.empty?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def published_edition
|
|
93
|
+
series.where(state: "published").order(version_number: "desc").first
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def previous_published_edition
|
|
97
|
+
series.where(state: "published").order(version_number: "desc").second
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def in_progress_sibling
|
|
101
|
+
subsequent_siblings.in_progress.order(version_number: "desc").first
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def can_create_new_edition?
|
|
105
|
+
subsequent_siblings.in_progress.empty?
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def meta_data
|
|
109
|
+
PublicationMetadata.new self
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def fact_check_email_address
|
|
113
|
+
FactCheckAddress.new.for_edition(self)
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def get_next_version_number
|
|
117
|
+
latest_version = series.order(version_number: "desc").first.version_number
|
|
118
|
+
latest_version + 1
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def indexable_content
|
|
122
|
+
respond_to?(:parts) ? indexable_content_with_parts : indexable_content_without_parts
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def indexable_content_without_parts
|
|
126
|
+
if respond_to?(:body)
|
|
127
|
+
"#{alternative_title} #{Govspeak::Document.new(body).to_text}".strip
|
|
128
|
+
else
|
|
129
|
+
alternative_title
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def indexable_content_with_parts
|
|
134
|
+
content = indexable_content_without_parts
|
|
135
|
+
return content unless published_edition
|
|
136
|
+
parts.inject([content]) { |acc, part|
|
|
137
|
+
acc.concat([part.title, Govspeak::Document.new(part.body).to_text])
|
|
138
|
+
}.compact.join(" ").strip
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# If the new clone is of the same type, we can copy all its fields over; if
|
|
142
|
+
# we are changing the type of the edition, any fields other than the base
|
|
143
|
+
# fields will likely be meaningless.
|
|
144
|
+
def fields_to_copy(edition_class)
|
|
145
|
+
edition_class == self.class ? self.class.fields_to_clone : []
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def build_clone(edition_class=nil)
|
|
149
|
+
unless state == "published"
|
|
150
|
+
raise "Cloning of non published edition not allowed"
|
|
151
|
+
end
|
|
152
|
+
unless can_create_new_edition?
|
|
153
|
+
raise "Cloning of a published edition when an in-progress edition exists
|
|
154
|
+
is not allowed"
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
edition_class = self.class unless edition_class
|
|
158
|
+
new_edition = edition_class.new(title: self.title,
|
|
159
|
+
version_number: get_next_version_number)
|
|
160
|
+
|
|
161
|
+
real_fields_to_merge = fields_to_copy(edition_class) +
|
|
162
|
+
[:panopticon_id, :overview, :alternative_title,
|
|
163
|
+
:slug, :section, :department]
|
|
164
|
+
|
|
165
|
+
real_fields_to_merge.each do |attr|
|
|
166
|
+
new_edition[attr] = read_attribute(attr)
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
if edition_class == AnswerEdition and self.class == GuideEdition
|
|
170
|
+
new_edition.body = whole_body
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
if edition_class == GuideEdition and self.class == AnswerEdition
|
|
174
|
+
new_edition.parts.build(title: "Part One", body: whole_body,
|
|
175
|
+
slug: "part-one")
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
new_edition
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def self.find_or_create_from_panopticon_data(panopticon_id,
|
|
182
|
+
importing_user, api_credentials)
|
|
183
|
+
existing_publication = Edition.where(panopticon_id: panopticon_id)
|
|
184
|
+
.order_by([:version_number, :desc]).first
|
|
185
|
+
return existing_publication if existing_publication
|
|
186
|
+
|
|
187
|
+
raise "Artefact not found" unless metadata = Artefact.find(panopticon_id)
|
|
188
|
+
|
|
189
|
+
importing_user.create_edition(metadata.kind.to_sym,
|
|
190
|
+
panopticon_id: metadata.id,
|
|
191
|
+
slug: metadata.slug,
|
|
192
|
+
title: metadata.name,
|
|
193
|
+
section: metadata.section,
|
|
194
|
+
department: metadata.department,
|
|
195
|
+
business_proposition: metadata.business_proposition)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def self.find_and_identify(slug, edition)
|
|
199
|
+
scope = where(slug: slug)
|
|
200
|
+
|
|
201
|
+
if edition.present? and edition == "latest"
|
|
202
|
+
scope.order_by(:version_number).last
|
|
203
|
+
elsif edition.present?
|
|
204
|
+
scope.where(version_number: edition).first
|
|
205
|
+
else
|
|
206
|
+
scope.where(state: "published").order_by(:created_at).last
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def panopticon_uri
|
|
211
|
+
Plek.current.find("panopticon") + "/artefacts/" + (panopticon_id || slug).to_s
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
def format
|
|
215
|
+
self.class.to_s.gsub("Edition", "")
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
def format_name
|
|
219
|
+
format
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def has_video?
|
|
223
|
+
false
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def safe_to_preview?
|
|
227
|
+
true
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def has_sibling_in_progress?
|
|
231
|
+
! sibling_in_progress.nil?
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
# Stop broadcasting a delete message unless there are no siblings.
|
|
235
|
+
def broadcast_action(callback_action)
|
|
236
|
+
unless callback_action == "destroyed" and self.siblings.any?
|
|
237
|
+
super(callback_action)
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
|
|
241
|
+
def was_published
|
|
242
|
+
previous_siblings.all.each(&:archive)
|
|
243
|
+
notify_siblings_of_published_edition
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def update_from_artefact(artefact)
|
|
247
|
+
self.title = artefact.name unless published?
|
|
248
|
+
self.slug = artefact.slug
|
|
249
|
+
self.section = artefact.section
|
|
250
|
+
self.department = artefact.department
|
|
251
|
+
self.business_proposition = artefact.business_proposition
|
|
252
|
+
self.save!
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
def check_for_archived_artefact
|
|
256
|
+
if panopticon_id
|
|
257
|
+
a = Artefact.find(panopticon_id)
|
|
258
|
+
if a.state == "archived" and changed_attributes.any?
|
|
259
|
+
# If we're only changing the state to archived, that's ok
|
|
260
|
+
# Any other changes are not allowed
|
|
261
|
+
allowed_keys = ["state", "updated_at"]
|
|
262
|
+
unless ((changed_attributes.keys - allowed_keys).empty?) and state == "archived"
|
|
263
|
+
raise "Editing of an edition with an Archived artefact is not allowed"
|
|
264
|
+
end
|
|
265
|
+
end
|
|
266
|
+
end
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
def artefact
|
|
270
|
+
Artefact.find(panopticon_id)
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
# When we delete an edition is the only one in its series
|
|
274
|
+
# we delete the associated artefact to remove all trace of the
|
|
275
|
+
# item from the system.
|
|
276
|
+
#
|
|
277
|
+
# We don't do this by notifying panopticon as this will only ever
|
|
278
|
+
# happen for artefacts representing editions that haven't been
|
|
279
|
+
# published (and therefore aren't registered in the rest of the)
|
|
280
|
+
# system.
|
|
281
|
+
def destroy_artefact
|
|
282
|
+
if can_destroy? && siblings.empty?
|
|
283
|
+
Artefact.find(self.panopticon_id).destroy
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
end
|