DomainTool.py 13.7 KB
Newer Older
Jean-Paul Smets's avatar
Jean-Paul Smets committed
1 2
##############################################################################
#
Romain Courteaud's avatar
Romain Courteaud committed
3
# Copyright (c) 2002, 2005 Nexedi SARL and Contributors. All Rights Reserved.
Jean-Paul Smets's avatar
Jean-Paul Smets committed
4
#                    Jean-Paul Smets-Solanes <jp@nexedi.com>
5
#                    Sebastien Robin <seb@nexedi.com>
Jean-Paul Smets's avatar
Jean-Paul Smets committed
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsability of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# garantees and support are strongly adviced to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################

from AccessControl import ClassSecurityInfo
from Globals import InitializeClass, DTMLFile
from Products.ERP5Type import Permissions
33
from Products.ERP5 import _dtmldir
Jean-Paul Smets's avatar
Jean-Paul Smets committed
34
from Products.ERP5Type.Tool.BaseTool import BaseTool
35
from zLOG import LOG
36
from DateTime import DateTime
Jean-Paul Smets's avatar
Jean-Paul Smets committed
37

38 39
_MARKER = []

Jean-Paul Smets's avatar
Jean-Paul Smets committed
40 41
class DomainTool(BaseTool):
    """
Jean-Paul Smets's avatar
Jean-Paul Smets committed
42 43
        A tool to define reusable ranges and subranges through
        predicate trees
Jean-Paul Smets's avatar
Jean-Paul Smets committed
44 45 46 47
    """
    id = 'portal_domains'
    meta_type = 'ERP5 Domain Tool'    
    portal_type     = 'Domain Tool'
Romain Courteaud's avatar
Romain Courteaud committed
48
    allowed_types   = ('ERP5 Domain', )
Jean-Paul Smets's avatar
Jean-Paul Smets committed
49 50 51 52

    # Declarative Security
    security = ClassSecurityInfo()

Romain Courteaud's avatar
Romain Courteaud committed
53 54
    security.declareProtected(Permissions.ManagePortal, 'manage_overview')
    manage_overview = DTMLFile('explainDomainTool', _dtmldir)
Jean-Paul Smets's avatar
Jean-Paul Smets committed
55

Romain Courteaud's avatar
Romain Courteaud committed
56 57
    # XXX FIXME method should not be public 
    # (some users are not able to see resource's price)
58
    security.declarePublic('searchPredicateList')
Romain Courteaud's avatar
Romain Courteaud committed
59
    def searchPredicateList(self, context, test=1, sort_method=None,
60 61 62
                            ignored_category_list=None,
                            tested_base_category_list=None,
                            filter_method=None, acquired=1, **kw):
63
      """
Romain Courteaud's avatar
Romain Courteaud committed
64 65
      Search all predicates which corresponds to this particular 
      context.
66
      
Romain Courteaud's avatar
Romain Courteaud committed
67
      - The sort_method parameter allows to give a method which will be
Vincent Pelletier's avatar
Vincent Pelletier committed
68
        used in order to sort the list of predicates found. The most
69 70
        important predicate is the first one in the list.

Romain Courteaud's avatar
Romain Courteaud committed
71
      - ignored_category_list:  this is the list of category that we do
72 73 74
        not want to test. For example, we might want to not test the 
        destination or the source of a predicate.

75 76 77 78
      - tested_base_category_list:  this is the list of category that we do
        want to test. For example, we might want to test only the 
        destination or the source of a predicate.

Romain Courteaud's avatar
Romain Courteaud committed
79
      - the acquired parameter allows to define if we want to use
80
        acquisition for categories. By default we want.
81 82 83 84 85 86 87
      """
      portal_catalog = context.portal_catalog
      portal_categories = context.portal_categories
      column_list = []
      expression_list = []
      checked_column_list = []
      sql_kw = {}
Romain Courteaud's avatar
Romain Courteaud committed
88
      # Search the columns of the predicate table
89 90
      for column in portal_catalog.getColumnIds():
        if column.startswith('predicate.'):
91
          column_list.append(column.split('.')[1])          
92
      for column in column_list:
Romain Courteaud's avatar
Romain Courteaud committed
93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
        if column not in checked_column_list:
          range_property = 0
          if (column.endswith('_range_min')) or \
             (column.endswith('_range_max')):
            range_property = 1
            # XXX FIXME: what means property here ?
            property = column[-len('_range_min')]
          if ('%s_range_min' % column) in column_list:
            range_property = 1
            property = column
          if range_property:
            # We have to check a range property
            base_name = 'predicate.%s' % property
#             LOG('searchPredicateList, getPath', 0, context.getPath())
#             LOG('searchPredicateList, base_name', 0, base_name)
#             LOG('searchPredicateList, property', 0, property)
#             LOG('searchPredicateList, getProperty', 0,
#                 context.getProperty(property))
            value = context.getProperty(property)
            format_dict = {'base_name': base_name}
            expression = "(%(base_name)s is NULL) AND " \
                         "(%(base_name)s_range_min is NULL) AND " \
                         "(%(base_name)s_range_max is NULL)" % format_dict
            if value is not None:
              # Handle Mysql datetime correctly
              if isinstance(value, DateTime):
                value = value.ISO()
              format_dict['value'] = value
              # Generate expression
              expression += "OR (%(base_name)s = '%(value)s') " \
                          "OR (%(base_name)s_range_min <= '%(value)s') AND " \
                              "(%(base_name)s_range_max is NULL) " \
                          "OR (%(base_name)s_range_min is NULL) AND " \
                              "%(base_name)s_range_max > '%(value)s' " \
                          "OR (%(base_name)s_range_min <= '%(value)s') AND " \
                              "%(base_name)s_range_max > '%(value)s' " \
                            % format_dict
            expression = '( %s )' % expression
            expression_list.append(expression)
            checked_column_list.append('%s' % property)
            checked_column_list.append('%s_range_min' % property)
            checked_column_list.append('%s_range_max' % property)
135 136
      # Add predicate.uid for automatic join
      sql_kw['predicate.uid'] = '!=0'
137
      where_expression = ' AND \n'.join(expression_list)
138 139

      # Add category selection
140 141 142 143 144
      if tested_base_category_list is None:
        if acquired:
          category_list = context.getAcquiredCategoryList()
        else:
          category_list = context.getCategoryList()
145
      else:
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
        category_list = []
        for tested_base_category in tested_base_category_list:
          category_list.extend(
               context.getCategoryMembershipList(tested_base_category, base=1))

      if tested_base_category_list != []:
        if len(category_list)==0:
          category_list = ['NULL']
        category_expression = portal_categories.buildSQLSelector(
                                           category_list,
                                           query_table='predicate_category')
        if len(where_expression) > 0:
          where_expression = '(%s) AND \n(%s)' % \
                                          (where_expression,category_expression)
        else:
          where_expression = category_expression

163 164 165 166
      sql_kw['where_expression'] = where_expression
      # Add predicate_category.uid for automatic join
      sql_kw['predicate_category.uid'] = '!=0'
      kw.update(sql_kw)
167
#       LOG('searchPredicateList, kw',0,kw)
168 169

      sql_result_list = portal_catalog.searchResults(**kw)
Romain Courteaud's avatar
Romain Courteaud committed
170
      if kw.get('src__'):
Sebastien Robin's avatar
Sebastien Robin committed
171
        return sql_result_list
172
      result_list = []
Romain Courteaud's avatar
Romain Courteaud committed
173 174
#       LOG('searchPredicateList, result_list before test', 0,
#           [x.getObject() for x in sql_result_list])
175
      for predicate in [x.getObject() for x in sql_result_list]:
176 177 178
        if test==0 or predicate.test(
                       context, 
                       tested_base_category_list=tested_base_category_list):
179
          result_list.append(predicate)
Romain Courteaud's avatar
Romain Courteaud committed
180
#       LOG('searchPredicateList, result_list before sort', 0, result_list)
Sebastien Robin's avatar
Sebastien Robin committed
181 182
      if filter_method is not None:
        result_list = filter_method(result_list)
Sebastien Robin's avatar
Sebastien Robin committed
183 184
      if sort_method is not None:
        result_list.sort(sort_method)
Romain Courteaud's avatar
Romain Courteaud committed
185
#       LOG('searchPredicateList, result_list after sort', 0, result_list)
186 187
      return result_list

Romain Courteaud's avatar
Romain Courteaud committed
188 189
    # XXX FIXME method should not be public 
    # (some users are not able to see resource's price)
190
    security.declarePublic('generateMappedValue')
Romain Courteaud's avatar
Romain Courteaud committed
191
    def generateMappedValue(self, context, test=1, predicate_list=None, **kw):
192
      """
Romain Courteaud's avatar
Romain Courteaud committed
193
      We will generate a mapped value with the list of all predicates 
Alexandre Boeglin's avatar
Alexandre Boeglin committed
194
      found. 
Romain Courteaud's avatar
Romain Courteaud committed
195
      Let's say we have 3 predicates (in the order we want) like this:
Sebastien Robin's avatar
Sebastien Robin committed
196 197 198
      Predicate 1   [ base_price1,           ,   ,   ,    ,    , ]
      Predicate 2   [ base_price2, quantity2 ,   ,   ,    ,    , ]
      Predicate 3   [ base_price3, quantity3 ,   ,   ,    ,    , ]
Alexandre Boeglin's avatar
Alexandre Boeglin committed
199
      Our generated MappedValue will have the base_price of the 
Romain Courteaud's avatar
Romain Courteaud committed
200 201 202
      predicate1, and the quantity of the Predicate2, because Predicate
      1 is the first one which defines a base_price and the Predicate2
      is the first one wich defines a quantity.
203
      """
Sebastien Robin's avatar
Sebastien Robin committed
204
      # First get the list of predicates
205
      if predicate_list is None:
Romain Courteaud's avatar
Romain Courteaud committed
206
        predicate_list = self.searchPredicateList(context, test=test, **kw)
207
      if len(predicate_list)==0:
Romain Courteaud's avatar
Romain Courteaud committed
208 209 210 211
        # No predicate, return None
        mapped_value = None
      else:
        # Generate tempDeliveryCell
212 213
        from Products.ERP5Type.Document import newTempSupplyCell
        mapped_value = newTempSupplyCell(self.getPortalObject(),
Romain Courteaud's avatar
Romain Courteaud committed
214 215 216 217 218 219 220 221 222 223 224 225
                                           'new_mapped_value')
        mapped_value_property_dict = {}
        # Look for each property the first predicate which defines the 
        # property
        for predicate in predicate_list:
          for mapped_value_property in predicate.getMappedValuePropertyList():
            if not mapped_value_property_dict.has_key(mapped_value_property):
              value = predicate.getProperty(mapped_value_property)
              if value is not None:
                mapped_value_property_dict[mapped_value_property] = value
        # Update mapped value
        mapped_value = mapped_value.asContext(**mapped_value_property_dict)
Sebastien Robin's avatar
Sebastien Robin committed
226
      return mapped_value
227

Alexandre Boeglin's avatar
Alexandre Boeglin committed
228 229 230
    # XXX FIXME method should not be public 
    # (some users are not able to see resource's price)
    security.declarePublic('generateMultivaluedMappedValue')
231 232
    def generateMultivaluedMappedValue(self, context, test=1,
        predicate_list=None, explanation_only=0, **kw):
Alexandre Boeglin's avatar
Alexandre Boeglin committed
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255
      """
      We will generate a mapped value with the list of all predicates 
      found. 
      Let's say we have 3 predicates (in the order we want) like this:
      Predicate 1   [ base_price1,           ,   ,   ,    ,    , ]
      Predicate 2   [ base_price2, additional_price2 ,   ,   ,    ,    , ]
      Predicate 3   [ base_price3, additional_price3 ,   ,   ,    ,    , ]
      Our generated MappedValue will take all values for each property and put
      them in lists, unless predicates define the same list of criterion categories
      """
      # First get the list of predicates
      if predicate_list is None:
        predicate_list = self.searchPredicateList(context, test=test, **kw)
      if len(predicate_list)==0:
        # No predicate, return None
        mapped_value = None
      else:
        # Generate tempDeliveryCell
        from Products.ERP5Type.Document import newTempSupplyCell
        mapped_value = newTempSupplyCell(self.getPortalObject(),
                                           'new_mapped_value')
        mapped_value_property_dict = {}
        processed_dict = {}
256
        explanation_dict = {}
Alexandre Boeglin's avatar
Alexandre Boeglin committed
257 258 259 260 261 262 263 264
        # Look for each property the first predicate with unique criterion
        # categories which defines the property
        for predicate in predicate_list:
          predicate_category_list = \
              tuple(predicate.getMembershipCriterionCategoryList())

          for mapped_value_property in predicate.getMappedValuePropertyList():
            prop_list = processed_dict.setdefault(predicate_category_list, [])
265 266
            full_prop_dict = explanation_dict.setdefault(
                predicate_category_list, {})
Alexandre Boeglin's avatar
Alexandre Boeglin committed
267 268 269 270 271 272
            if mapped_value_property in prop_list:
              # we already have one value for this (categories, property)
              continue

            value = predicate.getProperty(mapped_value_property)
            if value is not None:
273
              prop_list.append(mapped_value_property)
274
              full_prop_dict[mapped_value_property] = value
Alexandre Boeglin's avatar
Alexandre Boeglin committed
275 276 277 278
              mv_prop_list = \
                  mapped_value_property_dict.setdefault(
                  mapped_value_property, [])
              mv_prop_list.append(value)
279 280
        if explanation_only:
          return explanation_dict
Alexandre Boeglin's avatar
Alexandre Boeglin committed
281 282 283
        # Update mapped value
        mapped_value = mapped_value.asContext(**mapped_value_property_dict)
      return mapped_value
284 285


286
    def getChildDomainValueList(self, parent, **kw):
287 288 289 290 291 292
      """
      Return child domain objects already present adn thois generetaded dynamically
      """
      # get static domain
      object_list = list(parent.objectValues())
      # get dynamic object genretade from script
293
      object_list.extend(parent.getDomainGeneratorList(**kw))
294 295
      return object_list

296 297

    def getDomainByPath(self, path, default=_MARKER):
298 299 300
      """
      Return the domain object for a given path
      """
301 302
      path = path.split('/')
      base_domain_id = path[0]
303 304 305 306 307
      if default is _MARKER:
        domain = self[base_domain_id]
      else:
        domain = self.get(base_domain_id, _MARKER)
        if domain is _MARKER: return default
308 309 310
      for depth, subdomain in enumerate(path[1:]):
        domain_list = self.getChildDomainValueList(domain, depth=depth)
        for d in domain_list:
311 312
          if d.getId() == subdomain:
            domain = d
313 314
            break
        else:
315
          if domain is _MARKER: return default
316
          raise KeyError, subdomain
317 318
      return domain
  
Jean-Paul Smets's avatar
Jean-Paul Smets committed
319
InitializeClass(DomainTool)