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
        if column not in checked_column_list:
          range_property = 0
          if (column.endswith('_range_min')) or \
             (column.endswith('_range_max')):
            range_property = 1
98
            property = column[:-len('_range_min')]
Romain Courteaud's avatar
Romain Courteaud committed
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
          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)
134 135
      # Add predicate.uid for automatic join
      sql_kw['predicate.uid'] = '!=0'
136
      where_expression = ' AND \n'.join(expression_list)
137 138

      # Add category selection
139 140 141 142 143
      if tested_base_category_list is None:
        if acquired:
          category_list = context.getAcquiredCategoryList()
        else:
          category_list = context.getCategoryList()
144
      else:
145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161
        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

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

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

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

Alexandre Boeglin's avatar
Alexandre Boeglin committed
227 228 229
    # XXX FIXME method should not be public 
    # (some users are not able to see resource's price)
    security.declarePublic('generateMultivaluedMappedValue')
230 231
    def generateMultivaluedMappedValue(self, context, test=1,
        predicate_list=None, explanation_only=0, **kw):
Alexandre Boeglin's avatar
Alexandre Boeglin committed
232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254
      """
      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 = {}
255
        explanation_dict = {}
Alexandre Boeglin's avatar
Alexandre Boeglin committed
256 257 258 259 260 261 262 263
        # 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, [])
264 265
            full_prop_dict = explanation_dict.setdefault(
                predicate_category_list, {})
Alexandre Boeglin's avatar
Alexandre Boeglin committed
266 267 268 269 270 271
            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:
272
              prop_list.append(mapped_value_property)
273
              full_prop_dict[mapped_value_property] = value
Alexandre Boeglin's avatar
Alexandre Boeglin committed
274 275 276 277
              mv_prop_list = \
                  mapped_value_property_dict.setdefault(
                  mapped_value_property, [])
              mv_prop_list.append(value)
278 279
        if explanation_only:
          return explanation_dict
Alexandre Boeglin's avatar
Alexandre Boeglin committed
280 281 282
        # Update mapped value
        mapped_value = mapped_value.asContext(**mapped_value_property_dict)
      return mapped_value
283 284


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

295 296

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