add the export link + fix the bugs for the export module + a little bit of refactoring
This commit is contained in:
parent
aad4b09ad7
commit
02186d7368
20
app/controllers/admin/export_controller.rb
Normal file
20
app/controllers/admin/export_controller.rb
Normal file
@ -0,0 +1,20 @@
|
||||
module Admin
|
||||
class ExportController < BaseController
|
||||
|
||||
skip_load_and_authorize_resource
|
||||
|
||||
before_filter :authorize_export
|
||||
|
||||
def new
|
||||
zipfile = ::Locomotive::Export.run!(current_site, current_site.name.parameterize)
|
||||
send_file zipfile, :type => 'application/zip', :disposition => 'attachment'
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def authorize_export
|
||||
authorize! :export, Site
|
||||
end
|
||||
|
||||
end
|
||||
end
|
@ -1,5 +1,5 @@
|
||||
module Admin
|
||||
class ImportsController < BaseController
|
||||
class ImportController < BaseController
|
||||
|
||||
sections 'settings', 'site'
|
||||
|
||||
@ -32,7 +32,7 @@ module Admin
|
||||
:reset => Boolean.set(params[:reset])
|
||||
})
|
||||
|
||||
flash[:notice] = t("flash.admin.imports.create.#{Locomotive.config.delayed_job ? 'notice' : 'done'}")
|
||||
flash[:notice] = t("flash.admin.import.create.#{Locomotive.config.delayed_job ? 'notice' : 'done'}")
|
||||
|
||||
redirect_to Locomotive.config.delayed_job ? admin_import_url : new_admin_import_url
|
||||
rescue Exception => e
|
@ -55,6 +55,8 @@ class Ability
|
||||
|
||||
can :import, Site
|
||||
|
||||
can :export, Site
|
||||
|
||||
can :point, Site
|
||||
|
||||
can :manage, Membership
|
||||
|
@ -28,9 +28,9 @@ module Extensions
|
||||
|
||||
module ClassMethods
|
||||
|
||||
# Warning: used only in read-only
|
||||
def quick_tree(site)
|
||||
pages = site.pages.minimal_attributes.order_by([[:depth, :asc], [:position, :asc]]).to_a
|
||||
# Warning: should be used only in read-only
|
||||
def quick_tree(site, minimal_attributes = true)
|
||||
pages = (minimal_attributes ? site.pages.minimal_attributes : site.pages).order_by([[:depth, :asc], [:position, :asc]]).to_a
|
||||
|
||||
tmp = []
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
|
||||
- if can?(:manage, @site)
|
||||
- content_for :buttons do
|
||||
= admin_button_tag :export, new_admin_export_url, :class => 'new'
|
||||
= admin_button_tag :import, new_admin_import_url, :class => 'new'
|
||||
= admin_button_tag t('.new_membership'), new_admin_membership_url, :class => 'new'
|
||||
|
||||
|
@ -262,7 +262,7 @@ de:
|
||||
title: Cross-Domain Authentifizierung
|
||||
notice: Du wirst zu der Webseite in ein paar Sekunden weitergeleitet.
|
||||
|
||||
imports:
|
||||
import:
|
||||
new:
|
||||
title: Import
|
||||
help: "Passe bitte auf, wenn du ein neues Template für deine bestehende Webseite hochlädst. Deine bestehenden Daten könnten verändert oder gelöscht werden!"
|
||||
|
@ -137,6 +137,7 @@ en:
|
||||
|
||||
current_sites:
|
||||
edit:
|
||||
export: export
|
||||
import: import
|
||||
new_membership: add account
|
||||
help: "The site name can be updated by clicking it. To apply your changes, click on the \"Update\" button."
|
||||
@ -251,7 +252,7 @@ en:
|
||||
title: Cross-domain authentication
|
||||
notice: You will be redirected to the website in a couple of seconds.
|
||||
|
||||
imports:
|
||||
import:
|
||||
new:
|
||||
title: Import site template
|
||||
help: "Be careful when you upload a new template for your existing website, your current data could be modified or even removed."
|
||||
|
@ -138,6 +138,7 @@ fr:
|
||||
|
||||
current_sites:
|
||||
edit:
|
||||
export: exporter
|
||||
import: importer
|
||||
new_membership: ajouter compte
|
||||
help: "Le nom du site est modifiable en cliquant dessus. Pour appliquer votre modification, cliquez après sur le bouton \"Modifier\""
|
||||
@ -249,7 +250,7 @@ fr:
|
||||
title: Transfert vers un autre site
|
||||
notice: Vous allez être redirigé(e) vers le site dans quelques secondes.
|
||||
|
||||
imports:
|
||||
import:
|
||||
new:
|
||||
title: Importer template de site
|
||||
help: "Faites attention quand vous envoyez un nouveau template sur votre site, les données de celui-ci pourront être modifiées voire même supprimées."
|
||||
|
@ -243,7 +243,7 @@ it:
|
||||
title: Autenticazione cross-domain
|
||||
notice: Sarai rediretto al sito web in pochi secondi.
|
||||
|
||||
imports:
|
||||
import:
|
||||
new:
|
||||
title: Importa
|
||||
help: "Fai attenzione quando carichi un nuovo tema per il tuo sito, gli attuali dati potrebbero essere modificati o eliminati."
|
||||
|
@ -237,7 +237,7 @@ pt-BR:
|
||||
title: Autenticação por mútiplos domínios.
|
||||
notice: Você será redirecionado para o site em alguns segundos.
|
||||
|
||||
imports:
|
||||
import:
|
||||
new:
|
||||
title: Importar
|
||||
help: "Tenha cuidado ao enviar um novo tema ao seu site que já existe, seus dados atuais podem ser atualizados ou até removidos."
|
||||
|
@ -99,7 +99,7 @@ de:
|
||||
create:
|
||||
alert: "Du musst dich einloggen"
|
||||
|
||||
imports:
|
||||
import:
|
||||
create:
|
||||
done: "Deine Webseite wurde erfolreich aktualisiert."
|
||||
notice: "Deine Webseite wird aktualisiert."
|
||||
|
@ -99,7 +99,7 @@ en:
|
||||
create:
|
||||
alert: "You need to sign in"
|
||||
|
||||
imports:
|
||||
import:
|
||||
create:
|
||||
done: "Your site was successfully updated."
|
||||
notice: "Your site is being updated."
|
||||
|
@ -99,7 +99,7 @@ fr:
|
||||
create:
|
||||
alert: "Vous devez vous authentifier"
|
||||
|
||||
imports:
|
||||
import:
|
||||
create:
|
||||
done: "Votre site a été mis à jour"
|
||||
notice: "Votre site est en train d'être mis à jour"
|
||||
|
@ -99,7 +99,7 @@ it:
|
||||
create:
|
||||
alert: "Devi accedere"
|
||||
|
||||
imports:
|
||||
import:
|
||||
create:
|
||||
done: "Il tuo sito è stato modificato con successo."
|
||||
notice: "Il tuo sito è in fase di aggiornamento."
|
||||
|
@ -99,7 +99,7 @@ pt-BR:
|
||||
create:
|
||||
alert: "Você precisa fazer o login"
|
||||
|
||||
imports:
|
||||
import:
|
||||
create:
|
||||
done: "Seu site foi atualizado com sucesso."
|
||||
notice: "Seu site está sento atualizado."
|
||||
|
@ -35,8 +35,6 @@ Rails.application.routes.draw do
|
||||
|
||||
resources :assets
|
||||
|
||||
resources :images, :controller => 'assets', :defaults => { :image => true }
|
||||
|
||||
resources :content_types
|
||||
|
||||
resources :contents, :path => 'content_types/:slug/contents' do
|
||||
@ -49,7 +47,9 @@ Rails.application.routes.draw do
|
||||
|
||||
resources :cross_domain_sessions, :only => [:new, :create]
|
||||
|
||||
resource :import, :only => [:new, :show, :create]
|
||||
resource :import, :only => [:new, :show, :create], :controller => 'import'
|
||||
|
||||
resource :export, :only => [:new], :controller => 'export'
|
||||
|
||||
# installation guide
|
||||
match '/installation' => 'installation#show', :defaults => { :step => 1 }, :as => :installation
|
||||
|
6
doc/TODO
6
doc/TODO
@ -61,9 +61,8 @@ x bugs
|
||||
x delete an item => okay
|
||||
x bug: duplicate fields (new entry) when errors in the content type form
|
||||
x tinyMCE => fullscreen
|
||||
- export site:
|
||||
- generate database.yml
|
||||
- rake task to import a remote template
|
||||
x export site
|
||||
x rake task to import a remote template
|
||||
|
||||
=> MERGE
|
||||
|
||||
@ -71,6 +70,7 @@ x tinyMCE => fullscreen
|
||||
- icon for redirection page in the pages section (back-office)
|
||||
- test and/or convert existing templates (the 2 of the themes section)
|
||||
- [bushido] guiders / welcome page / devise cas authentication (SSO)
|
||||
- remove withelist for assets since we've now roles
|
||||
|
||||
|
||||
BACKLOG:
|
||||
|
29
features/admin/authorization/export.feature
Normal file
29
features/admin/authorization/export.feature
Normal file
@ -0,0 +1,29 @@
|
||||
Feature: Exporting a Site
|
||||
In order to use the site outside the current locomotive instance
|
||||
As an admin, designer, or author
|
||||
I will be restricted based on my role
|
||||
|
||||
Background:
|
||||
Given I have the site: "test site" set up
|
||||
And I have a designer and an author
|
||||
|
||||
Scenario: As an unauthenticated user
|
||||
Given I am not authenticated
|
||||
When I go to export page
|
||||
Then I should see "Log in"
|
||||
|
||||
Scenario: Accessing exporting functionality as an Admin
|
||||
Given I am an authenticated "admin"
|
||||
When I go to the export page
|
||||
Then I should get a download with the filename "locomotive-test-website.zip"
|
||||
|
||||
Scenario: Accessing exporting functionality as a Designer
|
||||
Given I am an authenticated "designer"
|
||||
When I go to the export page
|
||||
Then I should get a download with the filename "locomotive-test-website.zip"
|
||||
|
||||
Scenario: Accessing exporting functionality as an Author
|
||||
Given I am an authenticated "author"
|
||||
When I go to the export page
|
||||
Then I should be on the pages list
|
||||
And I should see the access denied message
|
@ -1,3 +1,7 @@
|
||||
When /^I follow image link "([^"]*)"$/ do |img_alt|
|
||||
find(:xpath, "//img[@alt = '#{img_alt}']/parent::a").click()
|
||||
end
|
||||
|
||||
Then /^I should get a download with the filename "([^\"]*)"$/ do |filename|
|
||||
page.response_headers['Content-Disposition'].should include("filename=\"#{filename}\"")
|
||||
end
|
@ -27,6 +27,8 @@ module NavigationHelpers
|
||||
edit_admin_current_site_path
|
||||
when /import page/
|
||||
new_admin_import_path
|
||||
when /export page/
|
||||
new_admin_export_path
|
||||
when /the "(.*)" model list page/
|
||||
content_type = Site.first.content_types.where(:name => $1).first
|
||||
admin_contents_path(content_type.slug)
|
||||
|
@ -1,4 +1,5 @@
|
||||
require 'fileutils'
|
||||
require 'zip/zip'
|
||||
|
||||
# Just a simple helper class to export quickly an existing and live locomotive website.
|
||||
# FIXME: will be replaced by the API in the next months
|
||||
@ -7,9 +8,10 @@ module Locomotive
|
||||
|
||||
@@instance = nil
|
||||
|
||||
def initialize(site)
|
||||
def initialize(site, filename = nil)
|
||||
@site = site
|
||||
@target_folder = File.join(Rails.root, 'tmp', 'export', Time.now.to_i)
|
||||
@filename = filename || Time.now.to_i.to_s
|
||||
@target_folder = File.join(Rails.root, 'tmp', 'export', filename)
|
||||
@site_hash = {} # used to generate the site.yml and compiled_site.yml files
|
||||
|
||||
self.create_target_folder
|
||||
@ -36,18 +38,32 @@ module Locomotive
|
||||
self.log('copying config files')
|
||||
self.copy_config_files
|
||||
|
||||
@target_folder
|
||||
self.log('generating the zip file')
|
||||
self.zip_it!
|
||||
end
|
||||
|
||||
# returns the path to the zipfile
|
||||
def self.run!(site)
|
||||
@@instance = self.new(site)
|
||||
def self.run!(site, filename = nil)
|
||||
@@instance = self.new(site, filename)
|
||||
@@instance.run!
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def zip_it!
|
||||
"#{@target_folder}.zip".tap do |dst|
|
||||
FileUtils.rm(dst, :force => true)
|
||||
::Zip::ZipFile.open(dst, ::Zip::ZipFile::CREATE) do |zipfile|
|
||||
Dir[File.join(@target_folder, '**/*')].each do |file|
|
||||
entry = file.gsub(@target_folder + '/', '')
|
||||
zipfile.add(entry, file)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def create_target_folder
|
||||
FileUtils.rmdir(@target_folder)
|
||||
FileUtils.mkdir_p(@target_folder)
|
||||
%w(app/content_types app/views/snippets app/views/pages config data public).each do |f|
|
||||
FileUtils.mkdir_p(File.join(@target_folder, f))
|
||||
@ -68,7 +84,7 @@ module Locomotive
|
||||
f.write(yaml(@site_hash))
|
||||
end
|
||||
|
||||
@site_hash.delete(content_types)
|
||||
@site_hash['site'].delete('content_types')
|
||||
|
||||
File.open(File.join(self.config_folder, 'site.yml'), 'w') do |f|
|
||||
f.write(yaml(@site_hash))
|
||||
@ -76,7 +92,7 @@ module Locomotive
|
||||
end
|
||||
|
||||
def copy_pages
|
||||
@site.all_pages_in_once.each { |p| self._copy_pages(p) }
|
||||
Page.quick_tree(@site, false).each { |p| self._copy_pages(p) }
|
||||
end
|
||||
|
||||
def copy_snippets
|
||||
@ -89,13 +105,13 @@ module Locomotive
|
||||
|
||||
def copy_content_types
|
||||
@site.content_types.each do |content_type|
|
||||
attributes = self.extract_attributes(content_type, %w(description slug order_by order_direction highlighted_field_name group_by_field_name api_enabled))
|
||||
attributes = self.extract_attributes(content_type, %w(name description slug order_by order_direction highlighted_field_name group_by_field_name api_enabled))
|
||||
|
||||
# custom_fields
|
||||
fields = []
|
||||
content_type.content_custom_fields.each do |field|
|
||||
fields << {
|
||||
field._alias => self.extract_attributes(field, %w(label kind hints target))
|
||||
field._alias => self.extract_attributes(field, %w(label kind hint target))
|
||||
}
|
||||
end
|
||||
|
||||
@ -104,8 +120,6 @@ module Locomotive
|
||||
@site_hash['site']['content_types'][content_type.name] = attributes.clone
|
||||
|
||||
# [structure] copy it into its own file
|
||||
attributes['name'] = content_type.name
|
||||
|
||||
File.open(File.join(self.content_types_folder, "#{content_type.slug}.yml"), 'w') do |f|
|
||||
f.write(self.yaml(attributes))
|
||||
end
|
||||
@ -128,8 +142,8 @@ module Locomotive
|
||||
|
||||
self.copy_file_from_an_uploader(theme_asset.source, target_path) do |bytes|
|
||||
if theme_asset.stylesheet_or_javascript?
|
||||
base_url = theme_asset.source.url.gsub(theme_asset.local_path)
|
||||
bytes.gsub!(base_url, '/')
|
||||
base_url = theme_asset.source.url.gsub(theme_asset.local_path, '')
|
||||
bytes.gsub(base_url, '/')
|
||||
else
|
||||
bytes
|
||||
end
|
||||
@ -149,7 +163,9 @@ module Locomotive
|
||||
def _copy_pages(page)
|
||||
attributes = self.extract_attributes(page, %w{title seo_title meta_description meta_keywords listed redirect_url content_type published})
|
||||
|
||||
unless page.redirected?
|
||||
unless page.redirect?
|
||||
attributes.delete('redirect_url')
|
||||
|
||||
# add editable elements
|
||||
page.editable_elements.each do |element|
|
||||
next if element.disabled? || (element.from_parent && element.default_content == element.content)
|
||||
@ -159,9 +175,9 @@ module Locomotive
|
||||
case element
|
||||
when EditableShortText, EditableLongText
|
||||
el_attributes['content'] = self.replace_asset_urls_in(element.content)
|
||||
when EditableImage
|
||||
when EditableFile
|
||||
filepath = File.join('/', 'samples', element.source_filename)
|
||||
self.copy_file_from_an_uploader(element.source, File.join(self.samples_folder, filepath))
|
||||
self.copy_file_from_an_uploader(element.source, File.join(self.public_folder, filepath))
|
||||
el_attributes['content'] = filepath
|
||||
end
|
||||
|
||||
@ -179,7 +195,7 @@ module Locomotive
|
||||
end
|
||||
|
||||
def extract_attributes(object, fields)
|
||||
object.attributes.select { |k, v| fields.include?(k) }
|
||||
object.attributes.select { |k, v| fields.include?(k) }.delete_if { |k, v| v.blank? }
|
||||
end
|
||||
|
||||
def pages_folder
|
||||
@ -215,6 +231,8 @@ module Locomotive
|
||||
File.open(target_path, 'w') do |f|
|
||||
bytes = uploader.read
|
||||
|
||||
bytes ||= ''
|
||||
|
||||
bytes = block.call(bytes) if block_given?
|
||||
|
||||
bytes = bytes.force_encoding('UTF-8') if RUBY_VERSION =~ /1\.9/
|
||||
@ -228,7 +246,7 @@ module Locomotive
|
||||
highlighted_field_name = content_type.highlighted_field_name
|
||||
|
||||
content_type.contents.each do |content|
|
||||
hash = { content.highlighted_field_value => {} }
|
||||
hash = {}
|
||||
|
||||
content.custom_fields.each do |field|
|
||||
next if field._name == highlighted_field_name
|
||||
@ -249,7 +267,7 @@ module Locomotive
|
||||
end
|
||||
end
|
||||
|
||||
data << hash
|
||||
data << { content.highlighted_field_value => hash }
|
||||
end
|
||||
|
||||
data
|
||||
@ -257,12 +275,10 @@ module Locomotive
|
||||
|
||||
def replace_asset_urls_in(text)
|
||||
base_url = AssetUploader.new(Asset.new(:site => @site)).store_dir
|
||||
base_url.split('/').pop
|
||||
base_url.join('/')
|
||||
(base_url = base_url.split('/')).pop
|
||||
base_url = base_url.join('/')
|
||||
|
||||
changed_text = text.gsub(%r(#{base_url}/[a-z0-9]{10,}/), 'samples')
|
||||
|
||||
changed_text.starts_with?('/') ? changed_text : "/#{changed_text}"
|
||||
text.gsub(%r(#{base_url}/[a-z0-9]{10,}/), "#{base_url.starts_with?('http') ? '/' : ''}samples/")
|
||||
end
|
||||
|
||||
def yaml(hash_or_array)
|
||||
@ -276,7 +292,7 @@ module Locomotive
|
||||
end
|
||||
|
||||
def log(message, domain = '')
|
||||
head = "[export_template][#{site.name}]"
|
||||
head = "[export_template][#{@site.name}]"
|
||||
head += "[#{domain}]" unless domain.blank?
|
||||
::Locomotive::Logger.info "\t#{head} #{message}"
|
||||
end
|
||||
|
@ -95,7 +95,7 @@ module Locomotive
|
||||
uploader = self.get_uploader(@site)
|
||||
|
||||
begin
|
||||
if zipfile.is_a?(String) && zipfile =~ /^http:\/\//
|
||||
if zipfile.is_a?(String) && zipfile =~ /^https?:\/\//
|
||||
uploader.download!(zipfile)
|
||||
uploader.store!
|
||||
else
|
||||
|
@ -11,6 +11,23 @@ namespace :locomotive do
|
||||
puts '...done'
|
||||
end
|
||||
|
||||
desc 'Import a remote template described by its URL -- 2 options: SITE=name or id, RESET=by default false'
|
||||
task :import => :environment do
|
||||
url, site_name_or_id, reset = ENV['URL'], ENV['SITE'], Boolean.set(ENV['RESET']) || false
|
||||
|
||||
if url.blank? || (url =~ /https?:\/\//).nil?
|
||||
raise "URL is missing or it is not a valid http url."
|
||||
end
|
||||
|
||||
site = Site.find(site_name_or_id) || Site.where(:name => site_name_or_id).first || Site.first
|
||||
|
||||
if site.nil?
|
||||
raise "No site found. Please give a correct value (name or id) for the SITE env variable."
|
||||
end
|
||||
|
||||
::Locomotive::Import::Job.run!(url, site, { :samples => true, :reset => reset })
|
||||
end
|
||||
|
||||
namespace :upgrade do
|
||||
|
||||
desc 'Set roles to the existing users'
|
||||
@ -33,7 +50,7 @@ namespace :locomotive do
|
||||
task :remove_asset_collections => :environment do
|
||||
puts "Processing #{AssetCollection.count} asset collection(s)..."
|
||||
|
||||
Asset.destroy_all # TODO
|
||||
Asset.destroy_all
|
||||
|
||||
AssetCollection.all.each do |collection|
|
||||
site = Site.find(collection.attributes['site_id'])
|
||||
|
Loading…
Reference in New Issue
Block a user