commit fa116a1b44076e29791348378683a16b2085a71e
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Thu Nov 12 22:25:37 2009 +0100

    [fix/evol] correct a bug in the item creation when handling the parent parameter, allow specifying a default parent per object, and provides a new find_by_full_handle() handy method

diff --git a/TODO b/TODO
index 02f30bd..c3c81c8 100644
--- a/TODO
+++ b/TODO
@@ -1,9 +1,7 @@
 - ensure creating/modifying the root DSA object is possible (using the "root" handle" ?)
 - improve configuration:
   * allow global relations to share them among objects ???
-- select parent at creation with :parent if present, or use
-  a default location (object-specific) ? this could be integrated in
-  a common code to be able to relocate an item
+- move (relocate) an item
 - search by family relationship ?
 - handle language subtypes ? (would need the RFC2798 preferredLanguage
   parser out of the CyborgHood project)
diff --git a/bin/shadowwalker b/bin/shadowwalker
index 136bb53..6c51d9c 100755
--- a/bin/shadowwalker
+++ b/bin/shadowwalker
@@ -408,7 +408,6 @@ class CreateCommand < Command
 
     modification_done = LdapShadows::Manipulation.item_modify_from_strings(item, args)
 
-    # TODO: use default parent for such object if defined (-> predefined hook)
     # TODO: detect if parent specified and allowed (-> predefined hook)
     item.save!
     puts "Creation done."
diff --git a/conf/ldap_shadows/shadows/MilkyPond/objects/entity.conf b/conf/ldap_shadows/shadows/MilkyPond/objects/entity.conf
index 50f3641..d61c16e 100644
--- a/conf/ldap_shadows/shadows/MilkyPond/objects/entity.conf
+++ b/conf/ldap_shadows/shadows/MilkyPond/objects/entity.conf
@@ -7,6 +7,7 @@ mapping:
   sort_by: o
   associated_relations: ['foundersIndividuals', 'foundersEntities']
   parent_handle_restrictions: "^unit/Entities$"
+  default_parent: "unit/Entities"
 presentation:
   name_attribute: o
   hidden_attributes: ['founder']
diff --git a/data/ldap_shadows/default_config/object.yaml b/data/ldap_shadows/default_config/object.yaml
index bfe5aff..8f37c08 100644
--- a/data/ldap_shadows/default_config/object.yaml
+++ b/data/ldap_shadows/default_config/object.yaml
@@ -5,6 +5,7 @@ mapping:
   associated_attributes: []
   parent_handle_restrictions:
   parent_dn_restrictions:
+  default_parent:
 presentation:
   name_attribute:
   hidden_attributes: []
diff --git a/data/ldap_shadows/schema/object.yaml b/data/ldap_shadows/schema/object.yaml
index 6847f0b..f3ee200 100644
--- a/data/ldap_shadows/schema/object.yaml
+++ b/data/ldap_shadows/schema/object.yaml
@@ -20,6 +20,7 @@ mapping:
           - type: str
       "parent_handle_restrictions": {type: str}
       "parent_dn_restrictions": {type: str}
+      "default_parent": {type: str}
       =: {type: any}
   "presentation":
     type: map
diff --git a/lib/ldap_shadows/elements/object.rb b/lib/ldap_shadows/elements/object.rb
index 12450b2..e64b665 100755
--- a/lib/ldap_shadows/elements/object.rb
+++ b/lib/ldap_shadows/elements/object.rb
@@ -22,6 +22,8 @@ module Elements
   class LdapObject < ActiveLdap::Base
     include LdapElement
 
+    attr_reader :parent_changed
+
     @relations_info = {}
 
     class << self
@@ -31,6 +33,12 @@ module Elements
     # default
     ldap_mapping :prefix => '', :classes => ['top'], :scope => :sub
 
+    def initialize(attributes = nil)
+      super(attributes)
+
+      @parent_changed = false
+    end
+
     def handle
       name = self[dn_attribute] || self.attributes[dn_attribute] || self.dn
       name = name.first if name.is_a? Array
@@ -400,23 +408,14 @@ module Elements
 
     def modify_parent(op, parent_full_handle)
       case op
+      when '='
       when '+=', '-='
         raise PreProcessingError, _("This operator is not possible for parent")
       else
         raise SyntaxError, _("Unknown operator '%s'") % op
       end
 
-      # TODO: provide such facilities elsewhere
-      raise SyntaxError, _("Bad handle '%s' for parent") % name unless parent_full_handle =~ /^([a-zA-Z]+)\/(.+)$/
-      parent_obj_hdl = $1.downcase.singularize
-      parent_item_hdl = $2
-      parent_obj_klass = self.class.shadow.get_object(parent_obj_hdl)
-      raise PreProcessingError, _("No such object '%s'") % parent_obj_hdl if parent_obj_klass.nil?
-      begin
-        parent_item = parent_obj_klass.find(parent_item_hdl, :attributes => attr_list)
-      rescue ActiveLdap::EntryNotFound
-        raise PreProcessingError, _("No such item '%s/%s'") % [parent_obj_klass.handle, parent_item_hdl]
-      end
+      parent_item = self.class.find_by_full_handle(parent_full_handle)
 
       p_hdl_restr = self.class.parameters[:mapping][:parent_handle_restrictions]
       unless p_hdl_restr.nil? or parent_full_handle =~ Regexp.new(p_hdl_restr)
@@ -428,10 +427,27 @@ module Elements
       end
 
       if self.new_entry?
-        self.base = parent_item.dn_obj - parent_obj_klass.base_obj
+        self.base = parent_item.dn_obj - parent_item.class.base_obj
       else
         raise PreProcessingError, _("Moving items is not yet implemented")
       end
+
+      @parent_changed = true
+    end
+
+    def self.find_by_full_handle(full_handle)
+      raise PreProcessingError, _("Bad handle '%s'") % name unless full_handle =~ /^([a-zA-Z]+)\/(.+)$/
+      obj_hdl = $1.downcase.singularize
+      item_hdl = $2
+
+      obj_klass = self.shadow.get_object(obj_hdl)
+      raise PreProcessingError, _("No such object '%s'") % obj_hdl if obj_klass.nil?
+
+      begin
+        obj_klass.find(item_hdl, :attributes => [""])
+      rescue ActiveLdap::EntryNotFound
+        raise PreProcessingError, _("No such item '%s/%s'") % [obj_klass.handle, item_hdl]
+      end
     end
 
     def add_aspect(aspect_name)
@@ -494,6 +510,7 @@ module Elements
       check_hooks_before(:save)
       check_missing_attributes
       check_password
+      check_parent
     end
 
     def before_delete_jobs
@@ -553,8 +570,25 @@ module Elements
       self.userPassword = ActiveLdap::UserPassword.send(hash_func, self.userPassword)
     end
 
+    def check_parent
+      if self.new_entry? and not self.parent_changed
+        parent_full_handle = self.class.parameters[:mapping][:default_parent]
+        if parent_full_handle
+          begin
+            parent_item = self.class.find_by_full_handle(parent_full_handle)
+            self.base = parent_item.dn_obj - parent_item.class.base_obj
+          rescue
+            raise PreProcessingError, _("Cannot create the item: bad default parent for this kind of object: %s") % $!
+          end
+        else
+          raise PreProcessingError, _("Cannot create the item: parent not specified and no default for such object")
+        end
+      end
+    end
+
     def after_save_jobs
       check_hooks_after(:save)
+      @parent_changed = false
     end
 
     def after_delete_jobs
