now can add new sites / accounts + basic membership mechanism iimplemented + fix a lot of minor bugs + add more rspec tests
This commit is contained in:
parent
a0216dc75f
commit
9901f53e12
24
app/controllers/admin/accounts_controller.rb
Normal file
24
app/controllers/admin/accounts_controller.rb
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
module Admin
|
||||||
|
class AccountsController < BaseController
|
||||||
|
|
||||||
|
sections 'settings'
|
||||||
|
|
||||||
|
def new
|
||||||
|
@account = Account.new(:email => params[:email])
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@account = Account.new(params[:account])
|
||||||
|
|
||||||
|
if @account.save
|
||||||
|
current_site.memberships.create(:account => @account)
|
||||||
|
flash_success!
|
||||||
|
redirect_to edit_admin_current_site_url
|
||||||
|
else
|
||||||
|
flash_error!
|
||||||
|
render :action => 'new'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
@ -1,39 +1,55 @@
|
|||||||
class Admin::BaseController < ::ApplicationController
|
module Admin
|
||||||
|
class BaseController < ::ApplicationController
|
||||||
|
|
||||||
include Locomotive::Routing::SiteDispatcher
|
include Locomotive::Routing::SiteDispatcher
|
||||||
|
|
||||||
layout 'admin'
|
layout 'admin'
|
||||||
|
|
||||||
before_filter :authenticate_account!
|
before_filter :authenticate_account!
|
||||||
|
|
||||||
|
before_filter :require_site
|
||||||
|
|
||||||
|
before_filter :validate_site_membership
|
||||||
|
|
||||||
before_filter :require_site
|
before_filter :set_locale
|
||||||
|
|
||||||
helper_method :sections
|
helper_method :sections
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def flash_success!
|
def flash_success!(options = {})
|
||||||
flash[:success] = translate_flash_msg(:successful)
|
msg = translate_flash_msg(:successful)
|
||||||
end
|
(options.has_key?(:now) && options[:now] ? flash.now : flash)[:success] = msg
|
||||||
|
end
|
||||||
def flash_error!
|
|
||||||
flash[:error] = translate_flash_msg(:failed)
|
def flash_error!(options = { :now => true })
|
||||||
end
|
msg = translate_flash_msg(:failed)
|
||||||
|
(options.has_key?(:now) && options[:now] ? flash.now : flash)[:error] = msg
|
||||||
def translate_flash_msg(kind)
|
|
||||||
t("#{kind.to_s}_#{action_name}", :scope => [:admin, controller_name.underscore.gsub('/', '.'), :messages])
|
|
||||||
end
|
|
||||||
|
|
||||||
def self.sections(main, sub = nil)
|
|
||||||
write_inheritable_attribute(:sections, { :main => main, :sub => sub })
|
|
||||||
end
|
|
||||||
|
|
||||||
def sections(key = nil)
|
|
||||||
if !key.nil? && key.to_sym == :sub
|
|
||||||
self.class.read_inheritable_attribute(:sections)[:sub] || self.controller_name.dasherize
|
|
||||||
else
|
|
||||||
self.class.read_inheritable_attribute(:sections)[:main]
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
|
def translate_flash_msg(kind)
|
||||||
|
t("#{kind.to_s}_#{action_name}", :scope => [:admin, controller_name.underscore.gsub('/', '.'), :messages])
|
||||||
|
end
|
||||||
|
|
||||||
|
def self.sections(main, sub = nil)
|
||||||
|
before_filter do |c|
|
||||||
|
sub = sub.call(c) if sub.respond_to?(:call)
|
||||||
|
sections = { :main => main, :sub => sub }
|
||||||
|
c.instance_variable_set(:@admin_sections, sections)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def sections(key = nil)
|
||||||
|
if !key.nil? && key.to_sym == :sub
|
||||||
|
@admin_sections[:sub] || self.controller_name.dasherize
|
||||||
|
else
|
||||||
|
@admin_sections[:main]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def set_locale
|
||||||
|
I18n.locale = current_account.locale
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
33
app/controllers/admin/current_sites_controller.rb
Normal file
33
app/controllers/admin/current_sites_controller.rb
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
module Admin
|
||||||
|
class CurrentSitesController < BaseController
|
||||||
|
|
||||||
|
sections 'settings', 'site'
|
||||||
|
|
||||||
|
def edit
|
||||||
|
@site = current_site
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@site = current_site
|
||||||
|
if @site.update_attributes(params[:site])
|
||||||
|
flash_success!
|
||||||
|
redirect_to edit_admin_current_site_url(new_host_if_subdomain_changed)
|
||||||
|
else
|
||||||
|
flash_error!
|
||||||
|
render :action => :edit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def new_host_if_subdomain_changed
|
||||||
|
host_from_site = "#{@site.subdomain}.#{Locomotive.config.default_domain}"
|
||||||
|
if request.host == host_from_site
|
||||||
|
{}
|
||||||
|
else
|
||||||
|
{ :host => "#{host_from_site}:#{request.port}" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
@ -1,54 +1,56 @@
|
|||||||
class Admin::LayoutsController < Admin::BaseController
|
module Admin
|
||||||
|
class LayoutsController < BaseController
|
||||||
|
|
||||||
sections 'settings'
|
sections 'settings'
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@layouts = current_site.layouts
|
@layouts = current_site.layouts
|
||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
@layout = current_site.layouts.build
|
@layout = current_site.layouts.build
|
||||||
end
|
|
||||||
|
|
||||||
def edit
|
|
||||||
@layout = current_site.layouts.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
@layout = current_site.layouts.build(params[:layout])
|
|
||||||
|
|
||||||
if @layout.save
|
|
||||||
flash_success!
|
|
||||||
redirect_to edit_admin_layout_url(@layout)
|
|
||||||
else
|
|
||||||
flash_error!
|
|
||||||
render :action => 'new'
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
def edit
|
||||||
@layout = current_site.layouts.find(params[:id])
|
@layout = current_site.layouts.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@layout = current_site.layouts.build(params[:layout])
|
||||||
|
|
||||||
|
if @layout.save
|
||||||
|
flash_success!
|
||||||
|
redirect_to edit_admin_layout_url(@layout)
|
||||||
|
else
|
||||||
|
flash_error!
|
||||||
|
render :action => 'new'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@layout = current_site.layouts.find(params[:id])
|
||||||
|
|
||||||
if @layout.update_attributes(params[:layout])
|
if @layout.update_attributes(params[:layout])
|
||||||
flash_success!
|
flash_success!
|
||||||
redirect_to edit_admin_layout_url(@layout)
|
redirect_to edit_admin_layout_url(@layout)
|
||||||
else
|
else
|
||||||
flash_error!
|
flash_error!
|
||||||
render :action => "edit"
|
render :action => "edit"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@layout = current_site.layouts.find(params[:id])
|
@layout = current_site.layouts.find(params[:id])
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@layout.destroy
|
@layout.destroy
|
||||||
flash_success!
|
flash_success!
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
flash[:error] = e.to_s
|
flash[:error] = e.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to admin_layouts_url
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to admin_layouts_url
|
|
||||||
end
|
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
39
app/controllers/admin/memberships_controller.rb
Normal file
39
app/controllers/admin/memberships_controller.rb
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
module Admin
|
||||||
|
class MembershipsController < BaseController
|
||||||
|
|
||||||
|
sections 'settings'
|
||||||
|
|
||||||
|
def new
|
||||||
|
@membership = current_site.memberships.build
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@membership = current_site.memberships.build(params[:membership])
|
||||||
|
|
||||||
|
case @membership.action_to_take
|
||||||
|
when :create_account
|
||||||
|
redirect_to new_admin_account_url(:email => @membership.email)
|
||||||
|
when :save_it
|
||||||
|
current_site.save
|
||||||
|
flash_success!
|
||||||
|
redirect_to edit_admin_site_url
|
||||||
|
when :error
|
||||||
|
flash_error! :now => true
|
||||||
|
render :action => 'new'
|
||||||
|
when :nothing
|
||||||
|
flash[:error] = translate_flash_msg(:already_saved)
|
||||||
|
redirect_to edit_admin_site_url
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
current_site.memberships.find(params[:id]).destroy
|
||||||
|
current_site.save
|
||||||
|
|
||||||
|
flash_success!
|
||||||
|
|
||||||
|
redirect_to edit_admin_site_url
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
21
app/controllers/admin/my_accounts_controller.rb
Normal file
21
app/controllers/admin/my_accounts_controller.rb
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
module Admin
|
||||||
|
class MyAccountsController < BaseController
|
||||||
|
|
||||||
|
sections 'settings', 'account'
|
||||||
|
|
||||||
|
def edit
|
||||||
|
@account = current_account
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@account = current_account
|
||||||
|
if @account.update_attributes(params[:account])
|
||||||
|
flash_success!
|
||||||
|
redirect_to edit_admin_my_account_url
|
||||||
|
else
|
||||||
|
render :action => :edit
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
@ -1,10 +1,12 @@
|
|||||||
class Admin::PagePartsController < Admin::BaseController
|
module Admin
|
||||||
|
class PagePartsController < BaseController
|
||||||
|
|
||||||
layout nil
|
layout nil
|
||||||
|
|
||||||
def index
|
def index
|
||||||
parts = current_site.layouts.find(params[:layout_id]).parts
|
parts = current_site.layouts.find(params[:layout_id]).parts
|
||||||
render :json => { :parts => parts }
|
render :json => { :parts => parts }
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
end
|
@ -1,68 +1,70 @@
|
|||||||
class Admin::PagesController < Admin::BaseController
|
module Admin
|
||||||
|
class PagesController < BaseController
|
||||||
|
|
||||||
sections 'contents'
|
sections 'contents'
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@pages = current_site.pages.roots
|
@pages = current_site.pages.roots
|
||||||
end
|
|
||||||
|
|
||||||
def new
|
|
||||||
@page = current_site.pages.build
|
|
||||||
@page.parts << PagePart.build_body_part
|
|
||||||
end
|
|
||||||
|
|
||||||
def edit
|
|
||||||
@page = current_site.pages.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
@page = current_site.pages.build(params[:page])
|
|
||||||
|
|
||||||
if @page.save
|
|
||||||
flash_success!
|
|
||||||
redirect_to edit_admin_page_url(@page)
|
|
||||||
else
|
|
||||||
flash_error!
|
|
||||||
render :action => 'new'
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
def new
|
||||||
@page = current_site.pages.find(params[:id])
|
@page = current_site.pages.build
|
||||||
|
@page.parts << PagePart.build_body_part
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
@page = current_site.pages.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@page = current_site.pages.build(params[:page])
|
||||||
|
|
||||||
|
if @page.save
|
||||||
|
flash_success!
|
||||||
|
redirect_to edit_admin_page_url(@page)
|
||||||
|
else
|
||||||
|
flash_error!
|
||||||
|
render :action => 'new'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@page = current_site.pages.find(params[:id])
|
||||||
|
|
||||||
if @page.update_attributes(params[:page])
|
if @page.update_attributes(params[:page])
|
||||||
flash_success!
|
flash_success!
|
||||||
redirect_to edit_admin_page_url(@page)
|
redirect_to edit_admin_page_url(@page)
|
||||||
else
|
else
|
||||||
flash_error!
|
flash_error!
|
||||||
render :action => "edit"
|
render :action => "edit"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def sort
|
def sort
|
||||||
@page = current_site.pages.find(params[:id])
|
@page = current_site.pages.find(params[:id])
|
||||||
@page.sort_children!(params[:children])
|
@page.sort_children!(params[:children])
|
||||||
|
|
||||||
render :json => { :message => translate_flash_msg(:successful) }
|
render :json => { :message => translate_flash_msg(:successful) }
|
||||||
end
|
|
||||||
|
|
||||||
def get_path
|
|
||||||
page = current_site.pages.build(:parent => current_site.pages.find(params[:parent_id]), :slug => params[:slug].slugify)
|
|
||||||
|
|
||||||
render :json => { :url => page.url, :slug => page.slug }
|
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
|
||||||
@page = current_site.pages.find(params[:id])
|
|
||||||
|
|
||||||
begin
|
|
||||||
@page.destroy
|
|
||||||
flash_success!
|
|
||||||
rescue Exception => e
|
|
||||||
flash[:error] = e.to_s
|
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to admin_pages_url
|
|
||||||
end
|
|
||||||
|
|
||||||
|
def get_path
|
||||||
|
page = current_site.pages.build(:parent => current_site.pages.find(params[:parent_id]), :slug => params[:slug].slugify)
|
||||||
|
|
||||||
|
render :json => { :url => page.url, :slug => page.slug }
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@page = current_site.pages.find(params[:id])
|
||||||
|
|
||||||
|
begin
|
||||||
|
@page.destroy
|
||||||
|
flash_success!
|
||||||
|
rescue Exception => e
|
||||||
|
flash[:error] = e.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to admin_pages_url
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
@ -1,9 +1,11 @@
|
|||||||
class Admin::PasswordsController < Devise::PasswordsController
|
module Admin
|
||||||
|
class PasswordsController < Devise::PasswordsController
|
||||||
|
|
||||||
include Locomotive::Routing::SiteDispatcher
|
include Locomotive::Routing::SiteDispatcher
|
||||||
|
|
||||||
layout 'login'
|
layout 'login'
|
||||||
|
|
||||||
before_filter :require_site
|
before_filter :require_site
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
@ -1,15 +1,17 @@
|
|||||||
class Admin::SessionsController < Devise::SessionsController
|
module Admin
|
||||||
|
class SessionsController < Devise::SessionsController
|
||||||
|
|
||||||
include Locomotive::Routing::SiteDispatcher
|
include Locomotive::Routing::SiteDispatcher
|
||||||
|
|
||||||
layout 'login'
|
layout 'login'
|
||||||
|
|
||||||
before_filter :require_site
|
before_filter :require_site
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def after_sign_in_path_for(resource)
|
||||||
|
admin_pages_url
|
||||||
|
end
|
||||||
|
|
||||||
def after_sign_in_path_for(resource)
|
|
||||||
admin_pages_url
|
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
48
app/controllers/admin/sites_controller.rb
Normal file
48
app/controllers/admin/sites_controller.rb
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
module Admin
|
||||||
|
class SitesController < BaseController
|
||||||
|
|
||||||
|
sections 'settings'
|
||||||
|
|
||||||
|
def new
|
||||||
|
@site = Site.new
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@site = Site.new(params[:site])
|
||||||
|
|
||||||
|
if @site.save
|
||||||
|
@site.memberships.create :account => @current_account, :admin => true
|
||||||
|
flash_success!
|
||||||
|
redirect_to edit_admin_my_account_url
|
||||||
|
else
|
||||||
|
flash_error!
|
||||||
|
render :action => 'new'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy
|
||||||
|
@site = current_account.sites.detect { |s| s._id == params[:id] }
|
||||||
|
|
||||||
|
if @site != current_site
|
||||||
|
@site.destroy
|
||||||
|
flash_success!
|
||||||
|
else
|
||||||
|
flash_error!
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to edit_admin_my_account_url
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def new_host_if_subdomain_changed
|
||||||
|
host_from_site = "#{@site.subdomain}.#{Locomotive.config.default_domain}"
|
||||||
|
if request.host == host_from_site
|
||||||
|
{}
|
||||||
|
else
|
||||||
|
{ :host => "#{host_from_site}:#{request.port}" }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
@ -1,54 +1,56 @@
|
|||||||
class Admin::SnippetsController < Admin::BaseController
|
module Admin
|
||||||
|
class SnippetsController < BaseController
|
||||||
|
|
||||||
sections 'settings'
|
sections 'settings'
|
||||||
|
|
||||||
def index
|
def index
|
||||||
@snippets = current_site.snippets
|
@snippets = current_site.snippets
|
||||||
end
|
|
||||||
|
|
||||||
def new
|
|
||||||
@snippet = current_site.snippets.build
|
|
||||||
end
|
|
||||||
|
|
||||||
def edit
|
|
||||||
@snippet = current_site.snippets.find(params[:id])
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
@snippet = current_site.snippets.build(params[:snippet])
|
|
||||||
|
|
||||||
if @snippet.save
|
|
||||||
flash_success!
|
|
||||||
redirect_to edit_admin_snippet_url(@snippet)
|
|
||||||
else
|
|
||||||
flash_error!
|
|
||||||
render :action => 'new'
|
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def update
|
def new
|
||||||
@snippet = current_site.snippets.find(params[:id])
|
@snippet = current_site.snippets.build
|
||||||
|
end
|
||||||
|
|
||||||
|
def edit
|
||||||
|
@snippet = current_site.snippets.find(params[:id])
|
||||||
|
end
|
||||||
|
|
||||||
|
def create
|
||||||
|
@snippet = current_site.snippets.build(params[:snippet])
|
||||||
|
|
||||||
|
if @snippet.save
|
||||||
|
flash_success!
|
||||||
|
redirect_to edit_admin_snippet_url(@snippet)
|
||||||
|
else
|
||||||
|
flash_error!
|
||||||
|
render :action => 'new'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
@snippet = current_site.snippets.find(params[:id])
|
||||||
|
|
||||||
if @snippet.update_attributes(params[:snippet])
|
if @snippet.update_attributes(params[:snippet])
|
||||||
flash_success!
|
flash_success!
|
||||||
redirect_to edit_admin_snippet_url(@snippet)
|
redirect_to edit_admin_snippet_url(@snippet)
|
||||||
else
|
else
|
||||||
flash_error!
|
flash_error!
|
||||||
render :action => "edit"
|
render :action => "edit"
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
@snippet = current_site.snippets.find(params[:id])
|
@snippet = current_site.snippets.find(params[:id])
|
||||||
|
|
||||||
begin
|
begin
|
||||||
@snippet.destroy
|
@snippet.destroy
|
||||||
flash_success!
|
flash_success!
|
||||||
rescue Exception => e
|
rescue Exception => e
|
||||||
flash[:error] = e.to_s
|
flash[:error] = e.to_s
|
||||||
|
end
|
||||||
|
|
||||||
|
redirect_to admin_snippets_url
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to admin_snippets_url
|
|
||||||
end
|
|
||||||
|
|
||||||
|
end
|
||||||
end
|
end
|
8
app/helpers/admin/accounts_helper.rb
Normal file
8
app/helpers/admin/accounts_helper.rb
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
module Admin::AccountsHelper
|
||||||
|
|
||||||
|
def admin_on?(site = current_site)
|
||||||
|
site.memberships.detect { |a| a.admin? && a.account == current_account }
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
24
app/helpers/admin/sites_helper.rb
Normal file
24
app/helpers/admin/sites_helper.rb
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
module Admin::SitesHelper
|
||||||
|
|
||||||
|
def application_domain
|
||||||
|
domain = Locomotive.config.default_domain
|
||||||
|
domain += ":#{request.port}" if request.port != 80
|
||||||
|
domain
|
||||||
|
end
|
||||||
|
|
||||||
|
def main_site_url(site = current_site, options = {})
|
||||||
|
url = "http://#{site.subdomain}.#{Locomotive.config.default_domain}"
|
||||||
|
url += ":#{request.port}" if request.port != 80
|
||||||
|
url = File.join(url, controller.request.fullpath) if options.has_key?(:uri) && options[:uri]
|
||||||
|
url
|
||||||
|
end
|
||||||
|
|
||||||
|
def error_on_domain(site, name)
|
||||||
|
if (error = (site.errors[:domains] || []).detect { |n| n.include?(name) })
|
||||||
|
content_tag(:span, error, :class => 'inline-errors')
|
||||||
|
else
|
||||||
|
''
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -3,7 +3,7 @@ class Account
|
|||||||
include Mongoid::Timestamps
|
include Mongoid::Timestamps
|
||||||
|
|
||||||
# devise modules
|
# devise modules
|
||||||
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable #:registerable,
|
devise :database_authenticatable, :recoverable, :rememberable, :trackable, :validatable
|
||||||
|
|
||||||
# attr_accessible :email, :password, :password_confirmation # TODO
|
# attr_accessible :email, :password, :password_confirmation # TODO
|
||||||
|
|
||||||
@ -16,8 +16,31 @@ class Account
|
|||||||
|
|
||||||
## associations ##
|
## associations ##
|
||||||
|
|
||||||
|
## callbacks ##
|
||||||
|
before_destroy :remove_memberships!
|
||||||
|
|
||||||
|
## methods ##
|
||||||
|
|
||||||
def sites
|
def sites
|
||||||
Site.where({ :account_ids => self._id })
|
Site.where({ 'memberships.account_id' => self._id })
|
||||||
|
end
|
||||||
|
|
||||||
|
protected
|
||||||
|
|
||||||
|
def password_required?
|
||||||
|
!persisted? || !password.blank? || !password_confirmation.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
def remove_memberships!
|
||||||
|
self.sites.each do |site|
|
||||||
|
site.memberships.delete_if { |m| m.account_id == self._id }
|
||||||
|
|
||||||
|
if site.admin_memberships.empty?
|
||||||
|
raise I18n.t('errors.messages.needs_admin_account')
|
||||||
|
else
|
||||||
|
site.save
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
@ -28,7 +28,7 @@ class Layout < LiquidTemplate
|
|||||||
slug = part[0].strip.downcase
|
slug = part[0].strip.downcase
|
||||||
|
|
||||||
if slug == 'layout'
|
if slug == 'layout'
|
||||||
body = PagePart.new :slug => slug, :name => I18n.t('admin.shared.attributes.body')
|
body = PagePart.new :slug => slug, :name => I18n.t('attributes.defaults.page_parts.name')
|
||||||
else
|
else
|
||||||
self.parts.build :slug => slug, :name => (part[1] || slug).gsub("\"", '')
|
self.parts.build :slug => slug, :name => (part[1] || slug).gsub("\"", '')
|
||||||
end
|
end
|
||||||
|
36
app/models/membership.rb
Normal file
36
app/models/membership.rb
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
class Membership
|
||||||
|
include Mongoid::Document
|
||||||
|
include Mongoid::Timestamps
|
||||||
|
|
||||||
|
## fields ##
|
||||||
|
field :admin, :type => Boolean, :default => false
|
||||||
|
|
||||||
|
## associations ##
|
||||||
|
belongs_to_related :account
|
||||||
|
embedded_in :site, :inverse_of => :memberships
|
||||||
|
|
||||||
|
## validations ##
|
||||||
|
validates_presence_of :account
|
||||||
|
|
||||||
|
## methods ##
|
||||||
|
|
||||||
|
def email; @email; end
|
||||||
|
|
||||||
|
def email=(email)
|
||||||
|
@email = email
|
||||||
|
self.account = Account.where(:email => email).first
|
||||||
|
end
|
||||||
|
|
||||||
|
def action_to_take
|
||||||
|
if @email.blank?
|
||||||
|
:error
|
||||||
|
elsif self.account.nil?
|
||||||
|
:create_account
|
||||||
|
elsif self.site.memberships.find_all { |m| m.account_id == self.account_id }.size > 1
|
||||||
|
:nothing
|
||||||
|
else
|
||||||
|
:save_it
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -21,21 +21,21 @@ class Page
|
|||||||
before_validate :normalize_slug
|
before_validate :normalize_slug
|
||||||
before_save { |p| p.parent_id = nil if p.parent_id.blank? }
|
before_save { |p| p.parent_id = nil if p.parent_id.blank? }
|
||||||
before_save :change_parent
|
before_save :change_parent
|
||||||
|
before_create { |p| p.parts << PagePart.build_body_part }
|
||||||
before_create { |p| p.fix_position(false) }
|
before_create { |p| p.fix_position(false) }
|
||||||
before_create :add_to_list_bottom
|
before_create :add_to_list_bottom
|
||||||
# before_create :add_body_part
|
before_destroy :do_not_remove_index_and_404_pages
|
||||||
before_destroy :remove_from_list
|
before_destroy :remove_from_list
|
||||||
|
|
||||||
## validations ##
|
## validations ##
|
||||||
validates_presence_of :site, :title, :slug
|
validates_presence_of :site, :title, :slug
|
||||||
validates_uniqueness_of :slug, :scope => :site_id
|
validates_uniqueness_of :slug, :scope => [:site_id, :parent_id]
|
||||||
validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 }
|
validates_exclusion_of :slug, :in => Locomotive.config.reserved_slugs, :if => Proc.new { |p| p.depth == 0 }
|
||||||
|
|
||||||
## named scopes ##
|
## named scopes ##
|
||||||
|
|
||||||
## behaviours ##
|
## behaviours ##
|
||||||
acts_as_tree :order => ['position', 'asc']
|
acts_as_tree :order => ['position', 'asc']
|
||||||
# accepts_nested_attributes_for :parts, :allow_destroy => true
|
|
||||||
|
|
||||||
## methods ##
|
## methods ##
|
||||||
|
|
||||||
@ -50,10 +50,6 @@ class Page
|
|||||||
def parts_attributes=(attributes)
|
def parts_attributes=(attributes)
|
||||||
self.update_parts(attributes.values.map { |attrs| PagePart.new(attrs) })
|
self.update_parts(attributes.values.map { |attrs| PagePart.new(attrs) })
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_body_part
|
|
||||||
self.parts.build :name => 'body', :slug => 'layout', :value => '---body here---'
|
|
||||||
end
|
|
||||||
|
|
||||||
def parent=(owner) # missing in acts_as_tree
|
def parent=(owner) # missing in acts_as_tree
|
||||||
@_parent = owner
|
@_parent = owner
|
||||||
@ -87,6 +83,17 @@ class Page
|
|||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
|
def do_not_remove_index_and_404_pages
|
||||||
|
# safe_site = self.site rescue nil
|
||||||
|
|
||||||
|
# return if safe_site.nil?
|
||||||
|
return if (self.site rescue nil).nil?
|
||||||
|
|
||||||
|
if self.index? || self.not_found?
|
||||||
|
raise I18n.t('errors.messages.protected_page')
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def update_parts(parts)
|
def update_parts(parts)
|
||||||
performed = []
|
performed = []
|
||||||
|
|
||||||
@ -143,6 +150,8 @@ class Page
|
|||||||
end
|
end
|
||||||
|
|
||||||
def remove_from_list
|
def remove_from_list
|
||||||
|
return if (self.site rescue nil).nil?
|
||||||
|
|
||||||
Page.where(:parent_id => self.parent_id).and(:position.gt => self.position).each do |p|
|
Page.where(:parent_id => self.parent_id).and(:position.gt => self.position).each do |p|
|
||||||
p.position -= 1
|
p.position -= 1
|
||||||
p.save
|
p.save
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
class PagePart
|
class PagePart
|
||||||
include Mongoid::Document
|
include Mongoid::Document
|
||||||
# include Mongoid::Timestamps
|
|
||||||
|
|
||||||
## fields ##
|
## fields ##
|
||||||
field :name, :type => String
|
field :name, :type => String
|
||||||
@ -12,8 +11,6 @@ class PagePart
|
|||||||
## associations ##
|
## associations ##
|
||||||
embedded_in :page, :inverse_of => :parts
|
embedded_in :page, :inverse_of => :parts
|
||||||
|
|
||||||
# attr_accessor :_delete
|
|
||||||
|
|
||||||
## callbacks ##
|
## callbacks ##
|
||||||
# before_validate { |p| p.slug ||= p.name.slugify if p.name.present? }
|
# before_validate { |p| p.slug ||= p.name.slugify if p.name.present? }
|
||||||
|
|
||||||
@ -25,12 +22,11 @@ class PagePart
|
|||||||
|
|
||||||
## methods ##
|
## methods ##
|
||||||
|
|
||||||
# def _delete=(value)
|
|
||||||
# puts "set _delete #{value.inspect}"
|
|
||||||
# self.attributes[:_destroy] = true if %w(t 1 true).include?(value)
|
|
||||||
# end
|
|
||||||
|
|
||||||
def self.build_body_part
|
def self.build_body_part
|
||||||
self.new(:name => I18n.t('admin.shared.attributes.body'), :slug => 'layout')
|
self.new({
|
||||||
|
:name => I18n.t('attributes.defaults.page_parts.name'),
|
||||||
|
:value => I18n.t('attributes.defaults.pages.other.body'),
|
||||||
|
:slug => 'layout'
|
||||||
|
})
|
||||||
end
|
end
|
||||||
end
|
end
|
@ -6,12 +6,12 @@ class Site
|
|||||||
field :name
|
field :name
|
||||||
field :subdomain, :type => String
|
field :subdomain, :type => String
|
||||||
field :domains, :type => Array, :default => []
|
field :domains, :type => Array, :default => []
|
||||||
field :account_ids, :type => Array, :default => []
|
|
||||||
|
|
||||||
## associations ##
|
## associations ##
|
||||||
has_many_related :pages
|
has_many_related :pages
|
||||||
has_many_related :layouts
|
has_many_related :layouts
|
||||||
has_many_related :snippets
|
has_many_related :snippets
|
||||||
|
embeds_many :memberships
|
||||||
|
|
||||||
## validations ##
|
## validations ##
|
||||||
validates_presence_of :name, :subdomain
|
validates_presence_of :name, :subdomain
|
||||||
@ -21,23 +21,24 @@ class Site
|
|||||||
validate :domains_must_be_valid_and_unique
|
validate :domains_must_be_valid_and_unique
|
||||||
|
|
||||||
## callbacks ##
|
## callbacks ##
|
||||||
|
after_create :create_default_pages!
|
||||||
before_save :add_subdomain_to_domains
|
before_save :add_subdomain_to_domains
|
||||||
|
after_destroy :destroy_in_cascade!
|
||||||
|
|
||||||
## named scopes ##
|
## named scopes ##
|
||||||
named_scope :match_domain, lambda { |domain| { :where => { :domains => domain } } }
|
named_scope :match_domain, lambda { |domain| { :where => { :domains => domain } } }
|
||||||
named_scope :match_domain_with_exclusion_of, lambda { |domain, site| { :where => { :domains => domain, :id.ne => site.id } } }
|
named_scope :match_domain_with_exclusion_of, lambda { |domain, site| { :where => { :domains => domain, :_id.ne => site.id } } }
|
||||||
|
|
||||||
## behaviours ##
|
## behaviours ##
|
||||||
add_dirty_methods :domains
|
|
||||||
|
|
||||||
## methods ##
|
## methods ##
|
||||||
|
|
||||||
def accounts
|
def accounts
|
||||||
Account.criteria.in(:_id => self.account_ids)
|
Account.criteria.in(:_id => self.memberships.collect(&:account_id))
|
||||||
end
|
end
|
||||||
|
|
||||||
def accounts=(models_or_ids)
|
def admin_memberships
|
||||||
self.account_ids = [*models_or_ids].collect { |object| object.respond_to?(:to_i) ? object : object.id }.uniq
|
self.memberships.find_all { |m| m.admin? }
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_subdomain_to_domains
|
def add_subdomain_to_domains
|
||||||
@ -49,13 +50,17 @@ class Site
|
|||||||
(self.domains || []) - ["#{self.subdomain}.#{Locomotive.config.default_domain}"]
|
(self.domains || []) - ["#{self.subdomain}.#{Locomotive.config.default_domain}"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def domains_with_subdomain
|
||||||
|
((self.domains || []) + ["#{self.subdomain}.#{Locomotive.config.default_domain}"]).uniq
|
||||||
|
end
|
||||||
|
|
||||||
protected
|
protected
|
||||||
|
|
||||||
def domains_must_be_valid_and_unique
|
def domains_must_be_valid_and_unique
|
||||||
return if self.domains.empty? || (!self.new_record? && !self.domains_changed?)
|
return if self.domains.empty?
|
||||||
|
|
||||||
(self.domains_without_subdomain - (self.domains_was || [])) .each do |domain|
|
self.domains_without_subdomain.each do |domain|
|
||||||
if not self.class.match_domain_with_exclusion_of(domain, self).first.nil?
|
if not self.class.match_domain_with_exclusion_of(domain, self).empty?
|
||||||
self.errors.add(:domains, :domain_taken, :value => domain)
|
self.errors.add(:domains, :domain_taken, :value => domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -65,4 +70,20 @@ class Site
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def create_default_pages!
|
||||||
|
%w{index 404}.each do |slug|
|
||||||
|
self.pages.create({
|
||||||
|
:slug => slug,
|
||||||
|
:title => I18n.t("attributes.defaults.pages.#{slug}.title"),
|
||||||
|
:body => I18n.t("attributes.defaults.pages.#{slug}.body")
|
||||||
|
})
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def destroy_in_cascade!
|
||||||
|
%w{pages layouts snippets}.each do |association|
|
||||||
|
self.send(association).destroy_all
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
22
app/views/admin/accounts/new.html.haml
Normal file
22
app/views/admin/accounts/new.html.haml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
- title t('.title')
|
||||||
|
|
||||||
|
- content_for :submenu do
|
||||||
|
= render 'admin/shared/menu/settings'
|
||||||
|
|
||||||
|
%p= t('.help')
|
||||||
|
|
||||||
|
= semantic_form_for @account, :url => admin_accounts_url do |f|
|
||||||
|
|
||||||
|
= f.foldable_inputs :name => :information do
|
||||||
|
= f.input :name, :required => false
|
||||||
|
|
||||||
|
= f.foldable_inputs :name => :credentials do
|
||||||
|
= f.input :email, :required => false
|
||||||
|
|
||||||
|
= f.custom_input :password, :label => :new_password do
|
||||||
|
= f.password_field :password
|
||||||
|
|
||||||
|
= f.custom_input :password_confirmation, :label => :new_password_confirmation do
|
||||||
|
= f.password_field :password_confirmation
|
||||||
|
|
||||||
|
= render 'admin/shared/form_actions', :back_url => edit_admin_current_site_url, :button_label => :create
|
45
app/views/admin/current_sites/_form.html.haml
Normal file
45
app/views/admin/current_sites/_form.html.haml
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
- content_for :head do
|
||||||
|
= javascript_include_tag 'admin/site'
|
||||||
|
|
||||||
|
= f.foldable_inputs :name => :information, :style => "#{'display: none' unless @site.new_record?}" do
|
||||||
|
= f.input :name, :required => false
|
||||||
|
|
||||||
|
= f.foldable_inputs :name => :access_points, :class => 'editable-list off' do
|
||||||
|
|
||||||
|
= f.custom_input :subdomain, :css => 'path' do
|
||||||
|
%em
|
||||||
|
http://
|
||||||
|
= f.text_field :subdomain
|
||||||
|
\.
|
||||||
|
%em
|
||||||
|
= application_domain
|
||||||
|
|
||||||
|
- @site.domains_without_subdomain.each_with_index do |name, index|
|
||||||
|
%li{ :class => "item added #{'last' if index == @site.domains.size - 1}"}
|
||||||
|
%em
|
||||||
|
http://
|
||||||
|
= text_field_tag 'site[domains][]', name
|
||||||
|
|
||||||
|
= error_on_domain(@site, name)
|
||||||
|
%span.actions
|
||||||
|
= link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove first', :confirm => t('admin.messages.confirm')
|
||||||
|
|
||||||
|
%li.item.new
|
||||||
|
%em
|
||||||
|
http://
|
||||||
|
= text_field_tag 'label', t('formtastic.hints.site.domain_name'), :class => 'string label void'
|
||||||
|
|
||||||
|
%span.actions
|
||||||
|
= link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove first', :confirm => t('admin.messages.confirm')
|
||||||
|
%button{ :class => 'button light add', :type => 'button' }
|
||||||
|
%span= t('admin.buttons.new_item')
|
||||||
|
|
||||||
|
= f.foldable_inputs :name => :memberships, :class => 'memberships' do
|
||||||
|
- @site.memberships.each_with_index do |membership, index|
|
||||||
|
- account = membership.account
|
||||||
|
%li{ :class => "item #{'last' if index == @site.memberships.size - 1}" }
|
||||||
|
%strong= account.name
|
||||||
|
%em= account.email
|
||||||
|
- if account != current_account
|
||||||
|
%span.actions
|
||||||
|
= link_to image_tag('admin/form/icons/trash.png'), admin_membership_url(membership), :class => 'remove first', :confirm => t('admin.messages.confirm'), :method => :delete
|
15
app/views/admin/current_sites/edit.html.haml
Normal file
15
app/views/admin/current_sites/edit.html.haml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
- title link_to(@site.name.blank? ? @site.name_was : @site.name, '#', :rel => 'site_name', :title => t('.ask_for_name'), :class => 'editable')
|
||||||
|
|
||||||
|
- content_for :submenu do
|
||||||
|
= render 'admin/shared/menu/settings'
|
||||||
|
|
||||||
|
- content_for :buttons do
|
||||||
|
= admin_button_tag t('.new_membership'), new_admin_membership_url, :class => 'add'
|
||||||
|
|
||||||
|
%p= t('.help')
|
||||||
|
|
||||||
|
= semantic_form_for @site, :url => admin_current_site_url do |f|
|
||||||
|
|
||||||
|
= render 'form', :f => f
|
||||||
|
|
||||||
|
= render 'admin/shared/form_actions', :button_label => :update
|
15
app/views/admin/memberships/new.html.haml
Normal file
15
app/views/admin/memberships/new.html.haml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
- title t('.title')
|
||||||
|
|
||||||
|
- content_for :submenu do
|
||||||
|
= render 'admin/shared/menu/settings'
|
||||||
|
|
||||||
|
%p= t('.help')
|
||||||
|
|
||||||
|
= semantic_form_for @membership, :url => admin_memberships_url do |f|
|
||||||
|
|
||||||
|
= f.inputs :name => :membership_email, :class => 'inputs email' do
|
||||||
|
|
||||||
|
= f.custom_input :email, { :css => 'string full', :with_label => false } do
|
||||||
|
= f.text_field :email
|
||||||
|
|
||||||
|
= render 'admin/shared/form_actions', :back_url => edit_admin_current_site_url, :button_label => :create
|
42
app/views/admin/my_accounts/edit.html.haml
Normal file
42
app/views/admin/my_accounts/edit.html.haml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
- title link_to(@account.name.blank? ? @account.name_was : @account.name, '#', :rel => 'account_name', :title => t('.ask_for_name'), :class => 'editable')
|
||||||
|
|
||||||
|
- content_for :submenu do
|
||||||
|
= render 'admin/shared/menu/settings'
|
||||||
|
|
||||||
|
- content_for :buttons do
|
||||||
|
= admin_button_tag t('.new_site'), new_admin_site_url, :class => 'add'
|
||||||
|
|
||||||
|
%p= t('.help')
|
||||||
|
|
||||||
|
= semantic_form_for @account, :url => admin_my_account_url do |f|
|
||||||
|
|
||||||
|
= f.foldable_inputs :name => :information, :style => 'display: none' do
|
||||||
|
= f.input :name
|
||||||
|
|
||||||
|
= f.foldable_inputs :name => :credentials do
|
||||||
|
= f.input :email
|
||||||
|
= f.custom_input :password, :label => :new_password do
|
||||||
|
= f.password_field :password
|
||||||
|
= f.custom_input :password_confirmation, :label => :new_password_confirmation do
|
||||||
|
= f.password_field :password_confirmation
|
||||||
|
|
||||||
|
= f.foldable_inputs :name => :sites, :class => 'sites off' do
|
||||||
|
- @account.sites.each do |site|
|
||||||
|
%li{ :class => 'item' }
|
||||||
|
%strong= link_to site.name, main_site_url(site, :uri => true)
|
||||||
|
%em= site.domains.join(', ')
|
||||||
|
|
||||||
|
- if admin_on?(site) && site != current_site
|
||||||
|
%span{ :class => 'actions' }
|
||||||
|
= link_to image_tag('admin/form/icons/trash.png'), admin_site_url(site), :class => 'remove first', :confirm => t('admin.messages.confirm'), :method => :delete
|
||||||
|
|
||||||
|
= f.foldable_inputs :name => :language, :class => 'language' do
|
||||||
|
= f.custom_input :language, { :css => 'full', :with_label => false } do
|
||||||
|
- Locomotive.config.locales.each do |locale|
|
||||||
|
%span
|
||||||
|
= image_tag "admin/flags/#{locale}.png"
|
||||||
|
= f.radio_button :locale, locale
|
||||||
|
|
||||||
|
= t(".#{locale}")
|
||||||
|
|
||||||
|
= render 'admin/shared/form_actions', :button_label => :update
|
@ -15,4 +15,4 @@
|
|||||||
= link_to t('.link'), new_account_session_path(resource_name)
|
= link_to t('.link'), new_account_session_path(resource_name)
|
||||||
|
|
||||||
.footer
|
.footer
|
||||||
= submit_button_tag t('buttons.change_password')
|
= submit_button_tag t('admin.buttons.change_password')
|
||||||
|
@ -14,4 +14,4 @@
|
|||||||
= link_to t('.link'), new_account_session_path(resource_name)
|
= link_to t('.link'), new_account_session_path(resource_name)
|
||||||
|
|
||||||
.footer
|
.footer
|
||||||
= submit_button_tag t('buttons.send_password')
|
= submit_button_tag t('admin.buttons.send_password')
|
@ -12,4 +12,4 @@
|
|||||||
= link_to t('.link'), new_password_path(resource_name)
|
= link_to t('.link'), new_password_path(resource_name)
|
||||||
|
|
||||||
.footer
|
.footer
|
||||||
= submit_button_tag t('buttons.login')
|
= submit_button_tag t('admin.buttons.login')
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
.actions
|
.actions
|
||||||
.span-12
|
.span-12
|
||||||
%p
|
%p
|
||||||
= link_to escape_once('← ') + t('.back'), back_url
|
- if defined?(back_url)
|
||||||
|
= link_to escape_once('← ') + t('.back'), back_url
|
||||||
|
- else
|
||||||
|
|
||||||
|
|
||||||
.span-12.last
|
.span-12.last
|
||||||
%p
|
%p
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
%h1= link_to current_site.name, '#'
|
%h1= link_to current_site.name, '#'
|
||||||
|
|
||||||
#global-actions-bar
|
#global-actions-bar
|
||||||
= t('.welcome', :name => link_to(current_account.name, '#'))
|
= t('.welcome', :name => link_to(current_account.name, edit_admin_my_account_url))
|
||||||
%span= '|'
|
%span= '|'
|
||||||
= link_to t('.see'), '#'
|
= link_to t('.see'), main_site_url
|
||||||
%span= '|'
|
%span= '|'
|
||||||
= link_to t('.logout'), destroy_account_session_url, :confirm => t('admin.messages.confirm')
|
= link_to t('.logout'), destroy_account_session_url, :confirm => t('admin.messages.confirm')
|
@ -1,5 +1,5 @@
|
|||||||
%ul#menu
|
%ul#menu
|
||||||
= admin_menu_item('contents', admin_pages_url)
|
= admin_menu_item('contents', admin_pages_url)
|
||||||
= admin_menu_item('assets', '#')
|
= admin_menu_item('assets', '#')
|
||||||
= admin_menu_item('settings', admin_layouts_url)
|
= admin_menu_item('settings', edit_admin_current_site_url)
|
||||||
%li.clear
|
%li.clear
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
%ul
|
%ul
|
||||||
|
= admin_submenu_item 'site', edit_admin_current_site_url
|
||||||
= admin_submenu_item 'layouts', admin_layouts_url
|
= admin_submenu_item 'layouts', admin_layouts_url
|
||||||
= admin_submenu_item 'snippets', admin_snippets_url
|
= admin_submenu_item 'snippets', admin_snippets_url
|
||||||
|
= admin_submenu_item 'account', edit_admin_my_account_url
|
35
app/views/admin/sites/_form.html.haml
Normal file
35
app/views/admin/sites/_form.html.haml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
- content_for :head do
|
||||||
|
= javascript_include_tag 'admin/site'
|
||||||
|
|
||||||
|
= f.foldable_inputs :name => :information, :style => "#{'display: none' unless @site.new_record?}" do
|
||||||
|
= f.input :name, :required => false
|
||||||
|
|
||||||
|
= f.foldable_inputs :name => :access_points, :class => 'editable-list off' do
|
||||||
|
|
||||||
|
= f.custom_input :subdomain, :css => 'path' do
|
||||||
|
%em
|
||||||
|
http://
|
||||||
|
= f.text_field :subdomain
|
||||||
|
\.
|
||||||
|
%em
|
||||||
|
= application_domain
|
||||||
|
|
||||||
|
- @site.domains_without_subdomain.each_with_index do |name, index|
|
||||||
|
%li{ :class => "item added #{'last' if index == @site.domains.size - 1}"}
|
||||||
|
%em
|
||||||
|
http://
|
||||||
|
= text_field_tag 'site[domains][]', name
|
||||||
|
|
||||||
|
= error_on_domain(@site, name)
|
||||||
|
%span.actions
|
||||||
|
= link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove first', :confirm => t('admin.messages.confirm')
|
||||||
|
|
||||||
|
%li.item.new
|
||||||
|
%em
|
||||||
|
http://
|
||||||
|
= text_field_tag 'label', t('formtastic.hints.site.domain_name'), :class => 'string label void'
|
||||||
|
|
||||||
|
%span.actions
|
||||||
|
= link_to image_tag('admin/form/icons/trash.png'), '#', :class => 'remove first', :confirm => t('admin.messages.confirm')
|
||||||
|
%button{ :class => 'button light add', :type => 'button' }
|
||||||
|
%span= t('admin.buttons.new_item')
|
12
app/views/admin/sites/new.html.haml
Normal file
12
app/views/admin/sites/new.html.haml
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
- title t('.title')
|
||||||
|
|
||||||
|
- content_for :submenu do
|
||||||
|
= render 'admin/shared/menu/settings'
|
||||||
|
|
||||||
|
%p= t('.help')
|
||||||
|
|
||||||
|
= semantic_form_for @site, :url => admin_sites_url do |form|
|
||||||
|
|
||||||
|
= render 'form', :f => form
|
||||||
|
|
||||||
|
= render 'admin/shared/form_actions', :back_url => edit_admin_my_account_url, :button_label => :create
|
@ -43,6 +43,6 @@ module Locomotive
|
|||||||
# Configure sensitive parameters which will be filtered from the log file.
|
# Configure sensitive parameters which will be filtered from the log file.
|
||||||
config.filter_parameters << :password
|
config.filter_parameters << :password
|
||||||
|
|
||||||
config.secret_token = '968a457262807c64e3ed5609882e17a774b917f5bcf2d308bd37eac4ba4d416d5692e6b13d77523fddb94c1dd603f160db8492b86b5e0203240bf339fe2aeae4'
|
config.secret_token = '968a457262807c64e3ed5609882e17a774b917f5bcf2d308bd37eac4ba4d416d5692e6b13d77523fddb94c1dd603f160db8492b86b5e0203240bf339fe2aeae4'
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -5,5 +5,10 @@ Locomotive.configure do |config|
|
|||||||
config.default_domain = 'example.com'
|
config.default_domain = 'example.com'
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: embed it in Locomotive right after configure
|
# TODO: embed them in Locomotive right after configure
|
||||||
ActionMailer::Base.default_url_options[:host] = Locomotive.config.default_domain + (Rails.env.development? ? ':3000' : '')
|
ActionMailer::Base.default_url_options[:host] = Locomotive.config.default_domain + (Rails.env.development? ? ':3000' : '')
|
||||||
|
|
||||||
|
Rails.application.config.session_store :cookie_store, {
|
||||||
|
:key => '_locomotive_session',
|
||||||
|
:domain => ".#{Locomotive.config.default_domain}"
|
||||||
|
}
|
@ -20,12 +20,30 @@ module Mongoid #:nodoc:
|
|||||||
# Enabling scope in validates_uniqueness_of validation
|
# Enabling scope in validates_uniqueness_of validation
|
||||||
module Validations #:nodoc:
|
module Validations #:nodoc:
|
||||||
class UniquenessValidator < ActiveModel::EachValidator
|
class UniquenessValidator < ActiveModel::EachValidator
|
||||||
def validate_each(document, attribute, value, scope = nil)
|
def validate_each(document, attribute, value)
|
||||||
criteria = { attribute => value, :_id.ne => document._id }
|
conditions = { attribute => value, :_id.ne => document._id }
|
||||||
criteria[scope] = document.send(scope) if scope
|
|
||||||
return if document.class.where(criteria).empty?
|
if options.has_key?(:scope) && !options[:scope].nil?
|
||||||
document.errors.add(attribute, :taken, :default => options[:message], :value => value)
|
[*options[:scope]].each do |scoped_attr|
|
||||||
|
conditions[scoped_attr] = document.attributes[scoped_attr]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Rails.logger.debug "conditions = #{conditions.inspect} / #{options[:scope].inspect}"
|
||||||
|
|
||||||
|
return if document.class.where(conditions).empty?
|
||||||
|
|
||||||
|
# if document.new_record? || key_changed?(document)
|
||||||
|
document.errors.add(attribute, :taken, :default => options[:message], :value => value)
|
||||||
|
# end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# protected
|
||||||
|
# def key_changed?(document)
|
||||||
|
# (document.primary_key || {}).each do |key|
|
||||||
|
# return true if document.send("#{key}_changed?")
|
||||||
|
# end; false
|
||||||
|
# end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
Rails.application.config.session_store :cookie_store, {
|
|
||||||
:key => '_locomotive_session',
|
|
||||||
}
|
|
@ -1,5 +1,36 @@
|
|||||||
en:
|
en:
|
||||||
admin:
|
admin:
|
||||||
|
buttons:
|
||||||
|
login: Log in
|
||||||
|
send_password: Send
|
||||||
|
change_password: Update
|
||||||
|
new_item: "+ add"
|
||||||
|
|
||||||
|
messages:
|
||||||
|
confirm: Are you sure ?
|
||||||
|
|
||||||
|
shared:
|
||||||
|
header:
|
||||||
|
welcome: Welcome, {{name}}
|
||||||
|
see: See website
|
||||||
|
logout: Log out
|
||||||
|
menu:
|
||||||
|
contents: Contents
|
||||||
|
assets: Assets
|
||||||
|
settings: Settings
|
||||||
|
pages: Pages
|
||||||
|
layouts: Layouts
|
||||||
|
snippets: Snippets
|
||||||
|
account: My account
|
||||||
|
site: Site
|
||||||
|
footer:
|
||||||
|
developed_by: Developed by
|
||||||
|
powered_by: and Powered by
|
||||||
|
form_actions:
|
||||||
|
back: Back without saving
|
||||||
|
create: Create
|
||||||
|
update: Update
|
||||||
|
|
||||||
sessions:
|
sessions:
|
||||||
new:
|
new:
|
||||||
title: Login
|
title: Login
|
||||||
@ -16,38 +47,17 @@ en:
|
|||||||
title: Update my password
|
title: Update my password
|
||||||
link: "→ Back to login page"
|
link: "→ Back to login page"
|
||||||
password: "Your new password"
|
password: "Your new password"
|
||||||
password_confirmation: "Confirmation of your new password"
|
password_confirmation: "Confirmation of your new password"
|
||||||
|
|
||||||
messages:
|
|
||||||
confirm: Are you sure ?
|
|
||||||
|
|
||||||
shared:
|
|
||||||
header:
|
|
||||||
welcome: Welcome, {{name}}
|
|
||||||
see: See website
|
|
||||||
logout: Log out
|
|
||||||
menu:
|
|
||||||
contents: Contents
|
|
||||||
assets: Assets
|
|
||||||
settings: Settings
|
|
||||||
pages: Pages
|
|
||||||
layouts: Layouts
|
|
||||||
snippets: Snippets
|
|
||||||
footer:
|
|
||||||
developed_by: Developed by
|
|
||||||
powered_by: and Powered by
|
|
||||||
form_actions:
|
|
||||||
back: Back without saving
|
|
||||||
create: Create
|
|
||||||
update: Update
|
|
||||||
attributes:
|
|
||||||
body: Body
|
|
||||||
|
|
||||||
pages:
|
pages:
|
||||||
index:
|
index:
|
||||||
title: Listing pages
|
title: Listing pages
|
||||||
no_items: "There are no pages for now. Just click <a href=\"{{url}}\">here</a> to create the first one."
|
no_items: "There are no pages for now. Just click <a href=\"{{url}}\">here</a> to create the first one."
|
||||||
new: new page
|
new: new page
|
||||||
|
page:
|
||||||
|
updated_at: updated at
|
||||||
|
edit:
|
||||||
|
show: show
|
||||||
messages:
|
messages:
|
||||||
successful_create: "Page was successfully created."
|
successful_create: "Page was successfully created."
|
||||||
successful_update: "Page was successfully updated."
|
successful_update: "Page was successfully updated."
|
||||||
@ -64,20 +74,47 @@ en:
|
|||||||
no_items: "There are no snippets for now. Just click <a href=\"{{url}}\">here</a> to create the first one."
|
no_items: "There are no snippets for now. Just click <a href=\"{{url}}\">here</a> to create the first one."
|
||||||
new: new snippet
|
new: new snippet
|
||||||
|
|
||||||
|
sites:
|
||||||
|
new:
|
||||||
|
title: New site
|
||||||
|
|
||||||
|
current_sites:
|
||||||
|
edit:
|
||||||
|
new_membership: add account
|
||||||
|
|
||||||
|
memberships:
|
||||||
|
new:
|
||||||
|
title: New membership
|
||||||
|
help: "Please give the account email to add. If it does not exist, you will be redirected to the account creation form."
|
||||||
|
|
||||||
|
accounts:
|
||||||
|
new:
|
||||||
|
title: New account
|
||||||
|
|
||||||
|
my_accounts:
|
||||||
|
edit:
|
||||||
|
new_site: new site
|
||||||
|
en: English
|
||||||
|
fr: French
|
||||||
|
|
||||||
|
|
||||||
formtastic:
|
formtastic:
|
||||||
titles:
|
titles:
|
||||||
information: General information
|
information: General information
|
||||||
meta: Meta
|
meta: Meta
|
||||||
code: Code
|
code: Code
|
||||||
|
credentials: Credentials
|
||||||
|
language: Language
|
||||||
|
sites: Sites
|
||||||
|
access_points: Access points
|
||||||
|
memberships: Accounts
|
||||||
|
membership_email: Account email
|
||||||
hints:
|
hints:
|
||||||
page:
|
page:
|
||||||
keywords: "Meta keywords used within the head tag of the page. They are separeted by an empty space. Required for SEO."
|
keywords: "Meta keywords used within the head tag of the page. They are separeted by an empty space. Required for SEO."
|
||||||
description: "Meta description used within the head tag of the page. Required for SEO."
|
description: "Meta description used within the head tag of the page. Required for SEO."
|
||||||
snippet:
|
snippet:
|
||||||
slug: "You need to know it in order to insert the snippet inside a page or a layout"
|
slug: "You need to know it in order to insert the snippet inside a page or a layout"
|
||||||
|
site:
|
||||||
|
domain_name: "ex: locomotiveapp.org"
|
||||||
|
|
||||||
buttons:
|
|
||||||
login: Log in
|
|
||||||
send_password: Send
|
|
||||||
change_password: Update
|
|
||||||
|
|
@ -3,4 +3,20 @@ en:
|
|||||||
messages:
|
messages:
|
||||||
domain_taken: "{{value}} is already taken"
|
domain_taken: "{{value}} is already taken"
|
||||||
invalid_domain: "{{value}} is invalid"
|
invalid_domain: "{{value}} is invalid"
|
||||||
missing_content_for_layout: "should contain 'content_for_layout' liquid tag"
|
missing_content_for_layout: "should contain 'content_for_layout' liquid tag"
|
||||||
|
needs_admin_account: "One admin account is required at least"
|
||||||
|
protected_page: "You can not remove index or 404 pages"
|
||||||
|
|
||||||
|
attributes:
|
||||||
|
defaults:
|
||||||
|
pages:
|
||||||
|
index:
|
||||||
|
title: "Home page"
|
||||||
|
body: "Content of the home page"
|
||||||
|
"404":
|
||||||
|
title: "Page not found"
|
||||||
|
body: "Content of the 404 page"
|
||||||
|
other:
|
||||||
|
body: "Content goes here"
|
||||||
|
page_parts:
|
||||||
|
name: "Body"
|
||||||
|
@ -27,11 +27,17 @@ Locomotive::Application.routes.draw do |map|
|
|||||||
end
|
end
|
||||||
resources :snippets
|
resources :snippets
|
||||||
|
|
||||||
# get 'login' => 'sessions#new', :as => :new_account_session
|
resources :site
|
||||||
# post 'login' => 'sessions#create', :as => :account_session
|
|
||||||
# get 'logout' => 'sessions#destroy', :as => :destroy_account_session
|
resource :current_site
|
||||||
# resource :password, :only => [:new, :create, :edit, :update], :controller => 'devise/passwords'
|
|
||||||
|
resources :accounts
|
||||||
|
|
||||||
|
resource :my_account
|
||||||
|
|
||||||
|
resources :memberships
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# magic url
|
||||||
match '/' => 'pages#show'
|
match '/' => 'pages#show'
|
||||||
end
|
end
|
||||||
|
@ -1,2 +1,5 @@
|
|||||||
Site.create! :name => 'Locomotive test website', :subdomain => 'test'
|
account = Account.create! :name => 'Admin', :email => 'admin@locomotiveapp.org', :password => 'locomotive', :password_confirmation => 'locomotive'
|
||||||
Account.create :name => 'Admin', :email => 'admin@locomotiveapp.org', :password => 'locomotive', :password_confirmation => 'locomotive'
|
|
||||||
|
site = Site.new :name => 'Locomotive test website', :subdomain => 'test'
|
||||||
|
site.memberships.build :account => account, :admin => true
|
||||||
|
site.save!
|
||||||
|
38
doc/TODO
38
doc/TODO
@ -1,6 +1,3 @@
|
|||||||
- scoping
|
|
||||||
- devise messages in French
|
|
||||||
- localize devise emails
|
|
||||||
x admin layout
|
x admin layout
|
||||||
x logout button
|
x logout button
|
||||||
x slugify page
|
x slugify page
|
||||||
@ -22,12 +19,31 @@ x parts js/css:
|
|||||||
x page parts
|
x page parts
|
||||||
x layout part should be always in first
|
x layout part should be always in first
|
||||||
x pages section (CRUD)
|
x pages section (CRUD)
|
||||||
- slug unique within a folder
|
x my account section (part of settings)
|
||||||
- validates_uniqueness_of :slug, :scope => :id
|
x add new accounts
|
||||||
- refactoring page.rb => create module pagetree
|
x edit site settings
|
||||||
- my account section (part of settings)
|
x slug unique within a folder
|
||||||
- layouts section
|
x layouts section
|
||||||
- create 404 + index pages once a site is created
|
x create new site
|
||||||
- can not delete index + 404 pages
|
x share session accross domains (only subdomains)
|
||||||
|
x destroy site
|
||||||
|
x remove all pages, snippets, ...etc when destroying a website
|
||||||
|
x destroy account
|
||||||
|
x can not delete the only one admin account for a site
|
||||||
|
x create 404 + index pages once a site is created
|
||||||
|
x can not delete index + 404 pages
|
||||||
|
x validates_uniqueness_of :slug, :scope => :id
|
||||||
|
x domain scoping when authenticating
|
||||||
|
|
||||||
|
|
||||||
|
BACKLOG:
|
||||||
|
- liquid rendering engine
|
||||||
|
- theme assets
|
||||||
|
- assets collection
|
||||||
|
|
||||||
|
- devise messages in French
|
||||||
|
- localize devise emails
|
||||||
- refactoring admin crud (pages + layouts + snippets)
|
- refactoring admin crud (pages + layouts + snippets)
|
||||||
- remove all pages, snippets, ...etc when destroying a website
|
- refactoring page.rb => create module pagetree
|
||||||
|
|
||||||
|
|
@ -7,7 +7,8 @@ module Locomotive
|
|||||||
:default_domain => 'rails.local.fr',
|
:default_domain => 'rails.local.fr',
|
||||||
:reserved_subdomains => %w{www admin email blog webmail mail support help site sites},
|
:reserved_subdomains => %w{www admin email blog webmail mail support help site sites},
|
||||||
# :forbidden_paths => %w{layouts snippets stylesheets javascripts assets admin system api},
|
# :forbidden_paths => %w{layouts snippets stylesheets javascripts assets admin system api},
|
||||||
:reserved_slugs => %w{stylesheets javascripts assets admin images api pages}
|
:reserved_slugs => %w{stylesheets javascripts assets admin images api pages},
|
||||||
|
:locales => %w{en fr}
|
||||||
}
|
}
|
||||||
|
|
||||||
cattr_accessor :settings
|
cattr_accessor :settings
|
||||||
|
@ -19,17 +19,22 @@ module Locomotive
|
|||||||
protected
|
protected
|
||||||
|
|
||||||
def fetch_site
|
def fetch_site
|
||||||
@site = Site.match_domain(request.host).first
|
@current_site ||= Site.match_domain(request.host).first
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_site
|
def current_site
|
||||||
@site ||= fetch_site
|
@current_site || fetch_site
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_site
|
def require_site
|
||||||
redirect_to application_root_url and return false if current_site.nil?
|
redirect_to application_root_url and return false if current_site.nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_site_membership
|
||||||
|
return if current_site && current_site.accounts.include?(current_account)
|
||||||
|
redirect_to application_root_url
|
||||||
|
end
|
||||||
|
|
||||||
def application_root_url
|
def application_root_url
|
||||||
root_url(:host => Locomotive.config.default_domain, :port => request.port)
|
root_url(:host => Locomotive.config.default_domain, :port => request.port)
|
||||||
end
|
end
|
||||||
|
@ -23,4 +23,13 @@ class MiscFormBuilder < Formtastic::SemanticFormBuilder
|
|||||||
template.content_tag(:li, template.find_and_preserve(html), :class => "#{options[:css]} #{'error' unless @object.errors[name].empty?}")
|
template.content_tag(:li, template.find_and_preserve(html), :class => "#{options[:css]} #{'error' unless @object.errors[name].empty?}")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def inline_errors_on(method, options = nil)
|
||||||
|
if render_inline_errors?
|
||||||
|
errors = @object.errors[method.to_sym]
|
||||||
|
template.content_tag(:span, [*errors].to_sentence.untaint, :class => 'inline-errors') if errors.present?
|
||||||
|
else
|
||||||
|
nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
BIN
public/images/admin/flags/en.png
Normal file
BIN
public/images/admin/flags/en.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
public/images/admin/flags/fr.png
Normal file
BIN
public/images/admin/flags/fr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 909 B |
36
public/javascripts/admin/site.js
Normal file
36
public/javascripts/admin/site.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
$(document).ready(function() {
|
||||||
|
|
||||||
|
var defaultValue = $('fieldset.editable-list li.new input[type=text]').val();
|
||||||
|
|
||||||
|
/* __ fields ___ */
|
||||||
|
$('fieldset.editable-list li.new input[type=text]').focus(function() {
|
||||||
|
if ($(this).hasClass('void') && $(this).parents('li').hasClass('new'))
|
||||||
|
$(this).val('').removeClass('void');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('fieldset.editable-list li.new button').click(function() {
|
||||||
|
var lastRow = $(this).parents('li.new');
|
||||||
|
|
||||||
|
var currentValue = lastRow.find('input.label').val();
|
||||||
|
if (currentValue == defaultValue || currentValue == '') return;
|
||||||
|
|
||||||
|
var newRow = lastRow.clone(true).removeClass('new').addClass('added').insertBefore(lastRow);
|
||||||
|
|
||||||
|
// should copy the value of the select box
|
||||||
|
var input = newRow.find('input.label')
|
||||||
|
.attr('name', 'site[domains][]');
|
||||||
|
if (lastRow.find('input.label').val() == '') input.val("undefined");
|
||||||
|
|
||||||
|
// then reset the form
|
||||||
|
lastRow.find('input').val(defaultValue).addClass('void');
|
||||||
|
lastRow.find('select').val('input');
|
||||||
|
});
|
||||||
|
|
||||||
|
$('fieldset.editable-list li a.remove').click(function(e) {
|
||||||
|
if (confirm($(this).attr('data-confirm')))
|
||||||
|
$(this).parents('li').remove();
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
@ -217,7 +217,7 @@ form.formtastic fieldset ol li.item span.actions {
|
|||||||
|
|
||||||
/* ___ editable-list (content type fields and validations) ___ */
|
/* ___ editable-list (content type fields and validations) ___ */
|
||||||
|
|
||||||
form.formtastic fieldset.editable-list ol { padding-left: 20px; }
|
form.formtastic fieldset.editable-list ol { padding-left: 20px; padding-right: 20px; width: 880px; }
|
||||||
|
|
||||||
form.formtastic fieldset.editable-list ol li { margin-left: 0px !important; }
|
form.formtastic fieldset.editable-list ol li { margin-left: 0px !important; }
|
||||||
|
|
||||||
@ -282,6 +282,15 @@ form.formtastic fieldset.editable-list ol li.added input:focus {
|
|||||||
border: 1px solid #a6a8b8;
|
border: 1px solid #a6a8b8;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
form.formtastic fieldset.editable-list ol li.added .inline-errors {
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
padding: 2px 3px;
|
||||||
|
background: #FFE5E5;
|
||||||
|
color: #CE2525;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
form.formtastic fieldset.editable-list ol li.new {
|
form.formtastic fieldset.editable-list ol li.new {
|
||||||
height: 42px;
|
height: 42px;
|
||||||
background-image: url(/images/admin/form/big_item.png);
|
background-image: url(/images/admin/form/big_item.png);
|
||||||
|
@ -55,6 +55,6 @@
|
|||||||
#page-parts code textarea {
|
#page-parts code textarea {
|
||||||
width: 880px;
|
width: 880px;
|
||||||
height: 400px;
|
height: 400px;
|
||||||
background: transparent url(../../images/admin/form/field.png) repeat-x 0 0;
|
background: transparent url(/images/admin/form/field.png) repeat-x 0 0;
|
||||||
border: 0px;
|
border: 0px;
|
||||||
}
|
}
|
@ -4,7 +4,7 @@
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
color: black;
|
color: black;
|
||||||
background: white url(../../images/admin/form/field.png) repeat-x 0 0 !important;
|
background: white url(/images/admin/form/field.png) repeat-x 0 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre.code, .editbox {
|
pre.code, .editbox {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
color: black;
|
color: black;
|
||||||
background: white url(../../images/admin/form/field.png) repeat-x 0 0 !important;
|
background: white url(/images/admin/form/field.png) repeat-x 0 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
pre.code, .editbox {
|
pre.code, .editbox {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 10pt;
|
font-size: 10pt;
|
||||||
color: black;
|
color: black;
|
||||||
background: white url(../../images/admin/form/field.png) repeat-x 0 0 !important;
|
background: white url(/images/admin/form/field.png) repeat-x 0 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.editbox p {
|
.editbox p {
|
||||||
|
@ -14,6 +14,12 @@ Factory.define :account do |a|
|
|||||||
a.locale 'en'
|
a.locale 'en'
|
||||||
end
|
end
|
||||||
|
|
||||||
|
## Memberships ##
|
||||||
|
Factory.define :membership do |m|
|
||||||
|
m.association :account, :factory => :account
|
||||||
|
m.admin true
|
||||||
|
end
|
||||||
|
|
||||||
## Pages ##
|
## Pages ##
|
||||||
Factory.define :page do |p|
|
Factory.define :page do |p|
|
||||||
p.association :site, :factory => :site
|
p.association :site, :factory => :site
|
||||||
|
@ -17,8 +17,7 @@ describe Account do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it "should have a default locale" do
|
it "should have a default locale" do
|
||||||
account = Factory.build(:account, :locale => nil)
|
account = Account.new
|
||||||
account.should be_valid
|
|
||||||
account.locale.should == 'en'
|
account.locale.should == 'en'
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -32,9 +31,35 @@ describe Account do
|
|||||||
|
|
||||||
it 'should own many sites' do
|
it 'should own many sites' do
|
||||||
account = Factory(:account)
|
account = Factory(:account)
|
||||||
site_1 = Factory(:site, :accounts => account)
|
site_1 = Factory(:site, :memberships => [Membership.new(:account => account)])
|
||||||
site_2 = Factory(:site, :subdomain => 'foo', :accounts => account)
|
site_2 = Factory(:site, :subdomain => 'foo', :memberships => [Membership.new(:account => account)])
|
||||||
account.sites.should == [site_1, site_2]
|
account.sites.should == [site_1, site_2]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'deleting' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@account = Factory.build(:account)
|
||||||
|
@site_1 = Factory.build(:site, :subdomain => 'foo', :memberships => [Factory.build(:membership, :account => @account)])
|
||||||
|
@site_2 = Factory.build(:site, :subdomain => 'bar', :memberships => [Factory.build(:membership, :account => @account)])
|
||||||
|
@account.stubs(:sites).returns([@site_1, @site_2])
|
||||||
|
Site.any_instance.stubs(:save).returns(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should also delete memberships' do
|
||||||
|
Site.any_instance.stubs(:admin_memberships).returns(['junk'])
|
||||||
|
@account.destroy
|
||||||
|
@site_1.memberships.should be_empty
|
||||||
|
@site_2.memberships.should be_empty
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should raise an exception if account is the only remaining admin' do
|
||||||
|
@site_1.stubs(:admin_memberships).returns(['junk'])
|
||||||
|
lambda {
|
||||||
|
@account.destroy
|
||||||
|
}.should raise_error(Exception, "One admin account is required at least")
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
@ -8,7 +8,7 @@ describe LiquidTemplate do
|
|||||||
|
|
||||||
# Validations ##
|
# Validations ##
|
||||||
|
|
||||||
%w{site name slug value}.each do |field|
|
%w{site name value}.each do |field|
|
||||||
it "should validate presence of #{field}" do
|
it "should validate presence of #{field}" do
|
||||||
template = Factory.build(:liquid_template, field.to_sym => nil)
|
template = Factory.build(:liquid_template, field.to_sym => nil)
|
||||||
template.should_not be_valid
|
template.should_not be_valid
|
||||||
|
55
spec/models/membership_spec.rb
Normal file
55
spec/models/membership_spec.rb
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe Membership do
|
||||||
|
|
||||||
|
it 'should have a valid factory' do
|
||||||
|
Factory.build(:membership, :account => Factory.build(:account)).should be_valid
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should validate presence of account' do
|
||||||
|
membership = Factory.build(:membership, :account => nil)
|
||||||
|
membership.should_not be_valid
|
||||||
|
membership.errors[:account].should == ["can't be blank"]
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should assign account from email' do
|
||||||
|
Account.stubs(:where).returns([Factory.build(:account)])
|
||||||
|
Account.stubs(:find).returns(Factory.build(:account))
|
||||||
|
membership = Factory.build(:membership, :account => nil)
|
||||||
|
membership.email = 'bart@simpson.net'
|
||||||
|
membership.account.should_not be_nil
|
||||||
|
membership.account.name.should == 'Bart Simpson'
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'next action to take' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@membership = Factory.build(:membership, :site => Factory.build(:site))
|
||||||
|
@account = Factory.build(:account)
|
||||||
|
Account.stubs(:where).returns([@account])
|
||||||
|
Account.stubs(:find).returns(@account)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should tell error' do
|
||||||
|
@membership.action_to_take.should == :error
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should tell we need to create a new account' do
|
||||||
|
Account.stubs(:where).returns([])
|
||||||
|
@membership.email = 'homer@simpson'
|
||||||
|
@membership.action_to_take.should == :create_account
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should tell nothing to do' do
|
||||||
|
@membership.email = 'bart@simpson.net'
|
||||||
|
@membership.site.stubs(:memberships).returns([@membership, @membership])
|
||||||
|
@membership.action_to_take.should == :nothing
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should tell membership has to be saved' do
|
||||||
|
@membership.email = 'bart@simpson.net'
|
||||||
|
@membership.action_to_take.should == :save_it
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
@ -2,6 +2,10 @@ require 'spec_helper'
|
|||||||
|
|
||||||
describe Page do
|
describe Page do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
Site.any_instance.stubs(:create_default_pages!).returns(true)
|
||||||
|
end
|
||||||
|
|
||||||
it 'should have a valid factory' do
|
it 'should have a valid factory' do
|
||||||
Factory.build(:page).should be_valid
|
Factory.build(:page).should be_valid
|
||||||
end
|
end
|
||||||
@ -28,6 +32,17 @@ describe Page do
|
|||||||
page.errors[:slug].should == ["is already taken"]
|
page.errors[:slug].should == ["is already taken"]
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'should validate uniqueness of slug within a "folder"' do
|
||||||
|
site = Factory(:site)
|
||||||
|
root = Factory(:page, :slug => 'index', :site => site)
|
||||||
|
child_1 = Factory(:page, :slug => 'first_child', :parent => root, :site => site)
|
||||||
|
(page = Factory.build(:page, :slug => 'first_child', :parent => root, :site => site)).should_not be_valid
|
||||||
|
page.errors[:slug].should == ["is already taken"]
|
||||||
|
|
||||||
|
page.slug = 'index'
|
||||||
|
page.valid?.should be_true
|
||||||
|
end
|
||||||
|
|
||||||
%w{admin stylesheets images javascripts}.each do |slug|
|
%w{admin stylesheets images javascripts}.each do |slug|
|
||||||
it "should consider '#{slug}' as invalid" do
|
it "should consider '#{slug}' as invalid" do
|
||||||
page = Factory.build(:page, :slug => slug)
|
page = Factory.build(:page, :slug => slug)
|
||||||
@ -61,6 +76,28 @@ describe Page do
|
|||||||
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'delete' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@page = Factory.build(:page)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should delete index page' do
|
||||||
|
@page.stubs(:index?).returns(true)
|
||||||
|
lambda {
|
||||||
|
@page.destroy
|
||||||
|
}.should raise_error(Exception, 'You can not remove index or 404 pages')
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should delete 404 page' do
|
||||||
|
@page.stubs(:not_found?).returns(true)
|
||||||
|
lambda {
|
||||||
|
@page.destroy
|
||||||
|
}.should raise_error(Exception, 'You can not remove index or 404 pages')
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
describe 'accepts_nested_attributes_for used for parts' do
|
describe 'accepts_nested_attributes_for used for parts' do
|
||||||
|
|
||||||
before(:each) do
|
before(:each) do
|
||||||
|
@ -47,9 +47,13 @@ describe Site do
|
|||||||
end
|
end
|
||||||
|
|
||||||
it 'should validate uniqueness of domains' do
|
it 'should validate uniqueness of domains' do
|
||||||
Factory(:site, :domains => %w{www.acme.net www.acme.com})
|
Factory(:site, :domains => %w{www.acme.net www.acme.com})
|
||||||
|
|
||||||
(site = Factory.build(:site, :domains => %w{www.acme.com})).should_not be_valid
|
(site = Factory.build(:site, :domains => %w{www.acme.com})).should_not be_valid
|
||||||
site.errors[:domains].should == ["www.acme.com is already taken"]
|
site.errors[:domains].should == ["www.acme.com is already taken"]
|
||||||
|
|
||||||
|
(site = Factory.build(:site, :subdomain => 'foo', :domains => %w{acme.example.com})).should_not be_valid
|
||||||
|
site.errors[:domains].should == ["acme.example.com is already taken"]
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'should validate format of domains' do
|
it 'should validate format of domains' do
|
||||||
@ -83,10 +87,11 @@ describe Site do
|
|||||||
## Associations ##
|
## Associations ##
|
||||||
|
|
||||||
it 'should have many accounts' do
|
it 'should have many accounts' do
|
||||||
account_1 = Factory(:account)
|
site = Factory.build(:site)
|
||||||
account_2 = Factory(:account, :name => 'homer', :email => 'homer@simpson.net')
|
account_1, account_2 = Factory(:account), Factory(:account, :name => 'homer', :email => 'homer@simpson.net')
|
||||||
site = Factory(:site, :accounts => [account_1, account_2, account_1])
|
site.memberships.build(:account => account_1, :admin => true)
|
||||||
site.account_ids.should == [account_1.id, account_2.id]
|
site.memberships.build(:account => account_2)
|
||||||
|
site.memberships.size.should == 2
|
||||||
site.accounts.should == [account_1, account_2]
|
site.accounts.should == [account_1, account_2]
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -98,4 +103,41 @@ describe Site do
|
|||||||
site.domains_without_subdomain.should == %w{www.acme.net www.acme.com}
|
site.domains_without_subdomain.should == %w{www.acme.net www.acme.com}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'once created' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@site = Factory(:site)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should create index and 404 pages' do
|
||||||
|
@site.pages.size.should == 2
|
||||||
|
@site.pages.first.slug.should == 'index'
|
||||||
|
@site.pages.last.slug.should == '404'
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'delete in cascade' do
|
||||||
|
|
||||||
|
before(:each) do
|
||||||
|
@site = Factory(:site)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should destroy pages' do
|
||||||
|
@site.pages.expects(:destroy_all)
|
||||||
|
@site.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should destroy layouts' do
|
||||||
|
@site.layouts.expects(:destroy_all)
|
||||||
|
@site.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'should destroy snippets' do
|
||||||
|
@site.snippets.expects(:destroy_all)
|
||||||
|
@site.destroy
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
Loading…
Reference in New Issue
Block a user