add the export link + fix the bugs for the export module + a little bit of refactoring

This commit is contained in:
did 2011-07-04 15:25:02 +02:00
parent aad4b09ad7
commit 02186d7368
25 changed files with 141 additions and 48 deletions

View 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

View File

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

View File

@ -55,6 +55,8 @@ class Ability
can :import, Site
can :export, Site
can :point, Site
can :manage, Membership

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

@ -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'])