~ubuntu-branches/ubuntu/hardy/gnue-common/hardy

« back to all changes in this revision

Viewing changes to src/datasources/GDataSource.py

  • Committer: Bazaar Package Importer
  • Author(s): Andrew Mitchell
  • Date: 2005-03-09 11:06:31 UTC
  • Revision ID: james.westby@ubuntu.com-20050309110631-8gvvn39q7tjz1kj6
Tags: upstream-0.5.14
ImportĀ upstreamĀ versionĀ 0.5.14

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# This file is part of GNU Enterprise.
 
3
#
 
4
# GNU Enterprise is free software; you can redistribute it
 
5
# and/or modify it under the terms of the GNU General Public
 
6
# License as published by the Free Software Foundation; either
 
7
# version 2, or (at your option) any later version.
 
8
#
 
9
# GNU Enterprise is distributed in the hope that it will be
 
10
# useful, but WITHOUT ANY WARRANTY; without even the implied
 
11
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 
12
# PURPOSE. See the GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public
 
15
# License along with program; see the file COPYING. If not,
 
16
# write to the Free Software Foundation, Inc., 59 Temple Place
 
17
# - Suite 330, Boston, MA 02111-1307, USA.
 
18
#
 
19
# Copyright 2000-2005 Free Software Foundation
 
20
#
 
21
# $Id: GDataSource.py 7080 2005-03-02 22:00:12Z reinhard $
 
22
 
 
23
"""
 
24
Class that implements a provider-independent DataSource object
 
25
"""
 
26
 
 
27
from gnue.common.apps import i18n, errors
 
28
from gnue.common.definitions import GObjects
 
29
import sys, string, types, cStringIO
 
30
from gnue.common.datasources import GConnections
 
31
from gnue.common.formatting import GTypecast
 
32
from gnue.common.datasources import GConditions, Exceptions
 
33
from gnue.common.definitions.GParserHelpers import GContent
 
34
from gnue.common.definitions.GParser import MarkupError
 
35
 
 
36
 
 
37
########################################################################
 
38
#
 
39
#
 
40
#
 
41
########################################################################
 
42
 
 
43
class GDataSource(GObjects.GObj):
 
44
  """
 
45
  Class that handles DataSources.  This is a subclass of GObj, which
 
46
  means this class can be created from XML markup and stored in an
 
47
  Object tree (e.g., a Forms tree).
 
48
  """
 
49
  def __init__(self, parent=None, type="GDataSource"):
 
50
    GObjects.GObj.__init__(self, parent, type)
 
51
    self.type = "object"
 
52
    self.connection = None
 
53
    self._connections = None
 
54
    self._dataObject = None
 
55
    self._connectionComment = ""
 
56
    self._fieldReferences = {}
 
57
    self._unboundFieldReferences = {}
 
58
    self._defaultValues = {}
 
59
    self._ignoreDispatchEvent = None
 
60
 
 
61
    self._inits =[self.primaryInit, self.secondaryInit, self.tertiaryInit]
 
62
    self._currentResultSet = None
 
63
    self._resultSetListeners = []
 
64
    self._toplevelParent = None # Needs to be set by subclass
 
65
                                # so that _topObject gets set
 
66
    self._topObject = None
 
67
    self.sorting = None
 
68
 
 
69
    #
 
70
    # trigger support
 
71
    #
 
72
    self._triggerGlobal = True
 
73
    self._triggerFunctions = {'createResultSet':{'function':self.createResultSet,
 
74
                                                 },
 
75
                              'simpleQuery':{'function':self.triggerSimpleQuery,
 
76
                                             },
 
77
                              'delete':{'function':self.deleteCurrentRecordsetEntry
 
78
                                        },
 
79
                              'call':{'function':self.callFuncOfCurrentRecordsetEntry
 
80
                                        },
 
81
                              'getCondition':{'function':self.getCondition},
 
82
                              'setCondition':{'function':self.setCondition},
 
83
                              'count' : {'function':self.triggerGetCount},
 
84
                              'update': {'function':
 
85
                                self.updateCurrentRecordSet}
 
86
                              }
 
87
 
 
88
    self._triggerProperties = {'extensions':{'get':self.getExtensions,
 
89
                                           'direct':1},
 
90
                                'recordCount':{'get':self.triggerGetCount,
 
91
                                               'direct':1},
 
92
                                'order_by':{'get':self.triggerGetOrderBy,
 
93
                                            'set':self.triggerSetOrderBy,
 
94
                                               'direct':1},
 
95
                               }
 
96
 
 
97
  def _ignoreDispatchEvent(self, *args, **parms):
 
98
    """
 
99
    A dummy method that _dispatchEvent is initially set to; does nothing
 
100
    """
 
101
    pass
 
102
 
 
103
  def setEventListener(self, listener):
 
104
    self._dispatchEvent = listener
 
105
    if self._dataObject:
 
106
      self._dataObject._dispatchEvent = listener
 
107
 
 
108
  def __getattr__(self, attr):
 
109
    if self._dataObject and attr[1] != '_' and hasattr(self._dataObject,attr):
 
110
      return getattr(self._dataObject,attr)
 
111
    else:
 
112
      raise AttributeError, attr
 
113
 
 
114
  def _buildObject(self):
 
115
 
 
116
    # Added 0.5.0 -- Delete before 1.0
 
117
    if hasattr(self,'database'):
 
118
      self.connection = self.database
 
119
      del self.database
 
120
 
 
121
    try:
 
122
      if len(self.explicitfields):
 
123
        for field in string.split(self.explicitfields,','):
 
124
          gDebug (7, "Explicit field %s" % field)
 
125
          self._fieldReferences[field] = True
 
126
    except AttributeError:
 
127
      pass
 
128
 
 
129
    self.__getSortOrder ()
 
130
    if self.sorting:
 
131
      for field in [item ['name'] for item in self.sorting]:
 
132
        self._fieldReferences [field] = True
 
133
 
 
134
    return GObjects.GObj._buildObject(self)
 
135
 
 
136
  def triggerGetOrderBy(self):
 
137
    return self.sorting
 
138
 
 
139
  def triggerSetOrderBy(self,value):
 
140
    self.sorting = self.__convertOrderBy (value)
 
141
    if self._dataObject:
 
142
      self._dataObject.sorting = self.sorting
 
143
 
 
144
  def triggerGetCount(self):
 
145
    if self._currentResultSet:
 
146
      return len(self._currentResultSet)
 
147
    else:
 
148
      return 0
 
149
 
 
150
  def triggerSimpleQuery(self,maskDict):
 
151
    queryDict = {}
 
152
    okToProcess = True
 
153
    for key in maskDict.keys():
 
154
      queryDict[key] = "%s" % maskDict[key]
 
155
      if not len(queryDict[key]):
 
156
        okToProcess = False
 
157
        break
 
158
 
 
159
    conditions = GConditions.buildConditionFromDict(queryDict,GConditions.GClike)
 
160
    resultSet = self.createResultSet(conditions)
 
161
    recordCount = resultSet.getRecordCount()
 
162
 
 
163
    returnList = []
 
164
    for count in range(recordCount):
 
165
      record = resultSet.getRecord(count)
 
166
      resultDict = {}
 
167
      for key in record._fields.keys():
 
168
        resultDict[key]=record.getField(key) or ""
 
169
      returnList.append(resultDict)
 
170
    return returnList
 
171
 
 
172
  def deleteCurrentRecordsetEntry(self):
 
173
    self._currentResultSet.getPostingRecordset().delete()
 
174
 
 
175
  def callFuncOfCurrentRecordsetEntry(self,name,params):
 
176
    n=self._currentResultSet.getRecordNumber()
 
177
    rset=self._currentResultSet.getRecord(n)
 
178
    if hasattr(rset,'callFunc'):
 
179
      return rset.callFunc(name,params)
 
180
    else:
 
181
      tmsg = u_("Backend doesn't support the trigger 'call' function")
 
182
      raise StandardError, tmsg
 
183
 
 
184
 
 
185
  # ---------------------------------------------------------------------------
 
186
  # Update the current record set
 
187
  # ---------------------------------------------------------------------------
 
188
 
 
189
  def updateCurrentRecordSet (self):
 
190
    """
 
191
    If a result set is available having a record all fields of this record
 
192
    would be updated. If the backend does not support this operation an
 
193
    ApplicationError will be raised.
 
194
    """
 
195
 
 
196
    if self._currentResultSet is not None:
 
197
      nr = self._currentResultSet.getRecordNumber ()
 
198
      rs = self._currentResultSet.getRecord (nr)
 
199
 
 
200
      if hasattr (rs, 'updateRecordSet'):
 
201
        rs.updateRecordSet ()
 
202
 
 
203
      else:
 
204
        raise errors.ApplicationError, \
 
205
            u_("Backend does not support the 'update' function")
 
206
 
 
207
  #
 
208
  # get/set the static condition assosiated with a datasource
 
209
  # the static condition is build out of the <condition> child
 
210
  # elements below a datasource XML definition
 
211
  #
 
212
  def setCondition(self, mycondition):
 
213
    self._dataObject._staticCondition = mycondition
 
214
#   dataObject.invalidateCachedConditions()
 
215
 
 
216
  def getCondition(self):
 
217
    return self._dataObject._staticCondition
 
218
 
 
219
  #
 
220
  # get the dbdriver extension object
 
221
  #
 
222
  def getExtensions(self):
 
223
    return self.extensions
 
224
 
 
225
  #
 
226
  # This method should be called after the object is created
 
227
  # but before any other methods are called
 
228
  #
 
229
  def setConnectionManager(self, connectionManager):
 
230
    self._connections = connectionManager
 
231
 
 
232
  def initialize(self):
 
233
    if not self.connection:
 
234
      # We are a connectionless datasource (virtual?)
 
235
      # We have to bind to something, so bind to empty or static driver
 
236
      if not self.type=="static":
 
237
        from gnue.common.datasources.drivers.special.unbound import Driver
 
238
        gDebug (7, 'Using empty data driver')
 
239
        dataObject = Driver.supportedDataObjects['object'](None)
 
240
 
 
241
      else:
 
242
        from gnue.common.datasources.drivers.special.static import Connection
 
243
        gDebug (7, 'Using static data driver')
 
244
        dataObject = Connection.supportedDataObjects['object'](None)
 
245
 
 
246
        for child in self._children:
 
247
          if isinstance(child, GStaticSet):
 
248
            dataObject._staticSet = child
 
249
            break
 
250
 
 
251
 
 
252
    elif self._connections:
 
253
      self.connection = string.lower(self.connection)
 
254
      # This will throw a GConnections.NotFoundError if an unknown
 
255
      # connection name is specified.  The calling method should
 
256
      # catch this exception and handle it properly (exit w/message)
 
257
      dataObject = \
 
258
         self._connections.getDataObject(self.connection, self.type)
 
259
      gDebug (7, "GDataSource.py bound to %s " % dataObject)
 
260
 
 
261
    self.name = string.lower(self.name)
 
262
    self._topObject._datasourceDictionary[self.name]=self
 
263
 
 
264
    dataObject._fieldReferences = self._fieldReferences
 
265
    dataObject._unboundFieldReferences = self._unboundFieldReferences
 
266
    dataObject._defaultValues = self._defaultValues
 
267
    dataObject._dataSource = self
 
268
 
 
269
    # TODO: Short-term hack to allow primary key support
 
270
    try:
 
271
      dataObject._primaryIdField = self.primarykey
 
272
      dataObject._primaryIdFormat = "%s = '%%s'" % self.primarykey
 
273
      dataObject._primaryIdChecked = True
 
274
    except AttributeError:
 
275
      pass
 
276
 
 
277
    hasRaw = False
 
278
    for child in self._children:
 
279
      if isinstance(child, GConditions.GCondition):
 
280
        dataObject._staticCondition = child
 
281
        break
 
282
      elif isinstance(child, GSql):
 
283
        dataObject._rawSQL = child
 
284
        hasRaw = True
 
285
 
 
286
 
 
287
    if self.type == "sql" and not hasRaw:
 
288
      raise Exceptions.InvalidDatasourceDefintion, \
 
289
        u_("Datasource %s is sql-based, but has no <sql> definition.") \
 
290
        % self.name
 
291
    elif not self.type == "sql" and hasRaw:
 
292
      raise Exceptions.InvalidDatasourceDefintion, \
 
293
        u_("Datasource %s is not sql-based, but has a <sql> definition.") \
 
294
        % self.name
 
295
 
 
296
 
 
297
    # Copy all attributes from XML to the dataObject
 
298
    tagAttributes = getXMLelements()['datasource']['Attributes']
 
299
    for attribute in tagAttributes.keys():
 
300
      if self.__dict__.has_key(attribute):
 
301
        dataObject.__dict__[attribute] = self.__dict__[attribute]
 
302
      else:
 
303
        try:
 
304
          dataObject.__dict__[attribute] = tagAttributes[attribute]['Default']
 
305
        except KeyError:
 
306
          pass
 
307
    self._dataObject = dataObject
 
308
    self._dataObject.sorting = self.sorting
 
309
 
 
310
 
 
311
  def connect(self):
 
312
    if self.connection != None:
 
313
      self._connections.requestConnection(self._dataObject)
 
314
 
 
315
 
 
316
  def getDataObject(self):
 
317
    return self._dataObject
 
318
 
 
319
 
 
320
  def referenceField(self, field, defaultValue=None):
 
321
 
 
322
    gDebug (7, 'Field %s implicitly referenced' % field)
 
323
    self._fieldReferences[field] = ""
 
324
 
 
325
    if defaultValue != None:
 
326
      self._defaultValues[field] = defaultValue
 
327
 
 
328
  def referenceFields(self, fields):
 
329
    for field in fields:
 
330
      if isinstance (field, types.StringType) or \
 
331
         isinstance (field, types.UnicodeType):
 
332
        self.referenceField(field)
 
333
      else:
 
334
        self.referenceField(*field)
 
335
 
 
336
  def referenceUnboundField(self, field, defaultValue=None):
 
337
 
 
338
    gDebug (7,'Unbound Field %s implicitly referenced' % field)
 
339
    self._unboundFieldReferences[field] = True
 
340
 
 
341
    if defaultValue != None:
 
342
      self._defaultValues[field] = defaultValue
 
343
 
 
344
 
 
345
  #
 
346
  # The following is a simple wrapper around the datasource's dataobject
 
347
  # to hide the dataobject from the app programmer
 
348
  #
 
349
  def hasMaster(self):
 
350
    return self._dataObject != None and self._dataObject.hasMaster()
 
351
 
 
352
  def createResultSet(self, conditions={}, readOnly=False, sql=""):
 
353
      resultSet= self._dataObject.createResultSet(conditions,readOnly,sql=sql)
 
354
      self.__setResultSet( resultSet )
 
355
      return resultSet
 
356
 
 
357
  def addDetailDataObject(self, dataObject, handler=None):
 
358
      self._dataObject.addDetailDataObject(dataObject, handler)
 
359
 
 
360
  def createEmptyResultSet(self, readOnly=False,masterRecordSet=None):
 
361
      resultSet = self._dataObject.createEmptyResultSet(readOnly, masterRecordSet=masterRecordSet)
 
362
      self.__setResultSet( resultSet )
 
363
      return resultSet
 
364
 
 
365
  def getQueryString(self,conditions={},forDetailSQL=None,additionalSQL=""):
 
366
    return self._dataObject.getQueryString(conditions, forDetailSQL,additionalSQL)
 
367
 
 
368
  #
 
369
  # Master/detail stuff
 
370
  #
 
371
 
 
372
  # Called by dbdrivers whenever this datasource's master has changed
 
373
  def masterResultSetChanged(self, masterResultSet, detailResultSet):
 
374
    self._masterResultSet = masterResultSet
 
375
    self.__setResultSet( detailResultSet )
 
376
 
 
377
  def __setResultSet(self, resultSet):
 
378
    self._currentResultSet = resultSet
 
379
    # Notify all the listeners (i.e., blocks) that the result set changed
 
380
    for listener in self._resultSetListeners:
 
381
      listener(resultSet)
 
382
 
 
383
  def registerResultSetListener(self, listener):
 
384
    self._resultSetListeners.append(listener)
 
385
 
 
386
  def primaryInit(self):
 
387
    self._topObject = self.findParentOfType(self._toplevelParent)
 
388
    gDebug (9, "Setting %s to connect mgr %s" \
 
389
        % (self.name, self._topObject._connections))
 
390
    self.__getSortOrder ()
 
391
    self.setConnectionManager(self._topObject._connections)
 
392
    self.initialize()
 
393
    self.connect()
 
394
    self.extensions = self._dataObject.triggerExtensions
 
395
 
 
396
 
 
397
  # TODO: Merged into GDataSource per the TODOs in reports and forms however
 
398
  # TODO: self._topObject._datasourceDictionary implies that the top object
 
399
  # TODO: always has a specifc structure.  This is a bad thing :(  Maybe GRootObj
 
400
  # TODO: should contain a getDatasourceDict()?
 
401
  #
 
402
  def secondaryInit(self):
 
403
 
 
404
    if hasattr(self, 'master') and self.master:
 
405
 
 
406
      self.master = string.lower(self.master)
 
407
      gDebug (7, "Linking detail '%s' to master '%s'" \
 
408
                 % (self.name, self.master))
 
409
 
 
410
      if self._topObject._datasourceDictionary.has_key(self.master):
 
411
        self._topObject._datasourceDictionary[self.master] \
 
412
            .getDataObject().addDetailDataObject(self.getDataObject(),
 
413
                                                 self)
 
414
      else:
 
415
        tmsg = u_("Detail source '%(source)s' references non-existant master "
 
416
                  "'%(master)s'") \
 
417
               % {'source': self.name,
 
418
                  'master': self.master}
 
419
        raise StandardError, tmsg
 
420
 
 
421
 
 
422
 
 
423
  def tertiaryInit(self):
 
424
    if hasattr(self, 'prequery'):
 
425
      if not self.hasMaster() and self.prequery:
 
426
        self.createResultSet()
 
427
 
 
428
 
 
429
  # ---------------------------------------------------------------------------
 
430
  # Make sure we have either no sort order, or one in the proper format
 
431
  # ---------------------------------------------------------------------------
 
432
 
 
433
  def __getSortOrder (self):
 
434
 
 
435
    # If there is both, an order_by attribute *and* a sorting-tag, we've stop
 
436
    child = self.findChildOfType ('GCSortOrder')
 
437
    if child is not None and hasattr (self, 'order_by'):
 
438
      raise MarkupError, \
 
439
          (u_("The use of order_by is depreciated. Please use <sortorder> "
 
440
              "instead"), self._url, self._lineNumber)
 
441
 
 
442
    # If there's a sorting tag, we'll use this first
 
443
    if child is not None:
 
444
      self.sorting = []
 
445
      for item in child.findChildrenOfType ('GCSortField'):
 
446
        self.sorting.append ({'name'      : item.name,
 
447
                              'descending': item.descending,
 
448
                              'ignorecase': item.ignorecase})
 
449
 
 
450
    # otherwise let's investigate the order_by attribute given
 
451
    elif hasattr (self, 'order_by'):
 
452
      self.sorting = self.__convertOrderBy (self.order_by)
 
453
      delattr (self, 'order_by')
 
454
 
 
455
 
 
456
  # ---------------------------------------------------------------------------
 
457
  # convert an order_by rule into the new 'sorting' format
 
458
  # ---------------------------------------------------------------------------
 
459
 
 
460
  def __convertOrderBy (self, order_by):
 
461
    """
 
462
    This function transforms an order_by rule into a proper 'sorting' sequence,
 
463
    made of tuples with fieldname and sort-direction.
 
464
 
 
465
    @param order_by: string, unicode-string, sequence with sort-order
 
466
    @return: sequence of tuples (field, direction)
 
467
    """
 
468
 
 
469
    result = []
 
470
 
 
471
    # If it's a string or a unicode string, we transform it into a tuple
 
472
    # sequence, where all items are treated to be in 'ascending' order
 
473
    if isinstance (order_by, types.StringType) or \
 
474
       isinstance (order_by, types.UnicodeType):
 
475
      gDebug (1, "DEPRECIATION WARNING: use of 'order_by' attribute is " \
 
476
                 "depreciated. Please use <sortorder> instead.")
 
477
      for field in string.split (order_by, ','):
 
478
        (item, desc) = (field, field [-5:].lower () == ' desc')
 
479
        if desc:
 
480
          item = item [:-5].strip ()
 
481
 
 
482
        # Since the old 'order_by' format does *not* support 'ignorecase' we
 
483
        # will use 'False' as default
 
484
        result.append ({'name': item, 'descending': desc})
 
485
 
 
486
    # Well, order_by is already a sequence. So we've to make sure it's a
 
487
    # sequence of tuples with fieldname and direction.
 
488
    elif isinstance (order_by, types.ListType):
 
489
      for item in order_by:
 
490
        if isinstance (item, types.StringType) or \
 
491
           isinstance (item, types.UnicodeType):
 
492
          result.append ({'name':item})
 
493
 
 
494
        elif isinstance (item, types.DictType):
 
495
          result.append (item)
 
496
 
 
497
        else:
 
498
          element = {'name': item [0]}
 
499
          if len (item) > 1: element ['descending'] = item [1]
 
500
          if len (item) > 2: element ['ignorecase'] = item [2]
 
501
 
 
502
    else:
 
503
      raise MarkupError, \
 
504
          (u_("Unknown type/format of 'order-by' attribute"), self._url,
 
505
            self._lineNumber)
 
506
 
 
507
    return result
 
508
 
 
509
 
 
510
 
 
511
  # Find a specific record in the resultset by field values
 
512
  def findRecord(self, fieldValues):
 
513
    self._currentResultSet.findRecord(fieldValues)
 
514
 
 
515
  #
 
516
  # Hooks for record-level triggers
 
517
  #
 
518
 
 
519
  def _beforeCommitInsert(self, record):
 
520
    return 1
 
521
 
 
522
  def _beforeCommitUpdate(self, record):
 
523
    return 1
 
524
 
 
525
  def _beforeCommitDelete(self, record):
 
526
    return 1
 
527
 
 
528
  def _onModification(self, record):
 
529
    return 1
 
530
 
 
531
  def _onRecordLoaded(self, record):
 
532
    return 1
 
533
 
 
534
 
 
535
  def close (self):
 
536
 
 
537
    # If we have a dataObject available, make sure it's reference to the
 
538
    # datasource will be cleared, so garbage collection can do it's job
 
539
    if self._dataObject is not None:
 
540
      self._dataObject._dataSource = None
 
541
 
 
542
    self._dataObject = None
 
543
 
 
544
    # Make sure we leave no unreachable reference !
 
545
    if self._topObject._datasourceDictionary.has_key (self.name):
 
546
      del self._topObject._datasourceDictionary
 
547
 
 
548
    self._fieldReferences = None
 
549
    self._unboundFieldReferences = None
 
550
    self._defaultValues = None
 
551
    self._ignoreDispatchEvent = None
 
552
 
 
553
    self._inits = None
 
554
    self._currentResultSet = None
 
555
    self._resultSetListeners = None
 
556
    self._toplevelParent = None 
 
557
    self._topObject = None
 
558
    self.sorting = None
 
559
 
 
560
    self._triggerFunctions = None
 
561
    self._triggerProperties = None
 
562
 
 
563
 
 
564
######
 
565
#
 
566
#
 
567
#
 
568
######
 
569
class GSql(GObjects.GObj):
 
570
  def __init__(self, parent=None):
 
571
     GObjects.GObj.__init__(self, parent, type="GDSql")
 
572
 
 
573
######
 
574
#
 
575
# Static Datasource Support
 
576
#
 
577
######
 
578
class GStaticSet(GObjects.GObj):
 
579
  def __init__(self, parent=None):
 
580
     GObjects.GObj.__init__(self, parent, type="GDStaticSet")
 
581
 
 
582
class GStaticSetRow(GObjects.GObj):
 
583
  def __init__(self, parent=None):
 
584
     GObjects.GObj.__init__(self, parent, type="GDStaticSetRow")
 
585
 
 
586
class GStaticSetField(GObjects.GObj):
 
587
  def __init__(self, parent=None):
 
588
     GObjects.GObj.__init__(self, parent, type="GDStaticSetField")
 
589
 
 
590
 
 
591
######
 
592
#
 
593
#
 
594
#
 
595
######
 
596
class GConnection(GObjects.GObj):
 
597
  def __init__(self, parent=None):
 
598
    GObjects.GObj.__init__(self, parent, "GCConnection")
 
599
    self.comment = ""
 
600
    self.name = ""
 
601
    self._inits =[self.initialize]
 
602
 
 
603
  def _buildObject(self):
 
604
    self.name = string.lower(self.name)
 
605
    return GObjects.GObj._buildObject(self)
 
606
 
 
607
  def initialize(self):
 
608
    # Add our database connection information to the connections
 
609
    # manager, then let it handle everything from there.
 
610
    root = self.findParentOfType(None)
 
611
    root._instance.connections.\
 
612
        addConnectionSpecification(self.name, {
 
613
           'name': self.name,
 
614
           'provider': self.provider,
 
615
           'dbname': self.dbname,
 
616
           'host': self.host } )
 
617
 
 
618
 
 
619
 
 
620
######
 
621
#
 
622
# Used by client GParsers to automatically pull supported xml tags
 
623
#
 
624
######
 
625
 
 
626
#
 
627
# Return any XML elements associated with
 
628
# GDataSources.  Bases is a dictionary of tags
 
629
# whose values are update dictionaries.
 
630
# For example: bases={'datasource': {'BaseClass':myDataSource}}
 
631
# sets xmlElements['datasource']['BaseClass'] = myDataSource
 
632
#
 
633
def getXMLelements(updates={}):
 
634
 
 
635
  xmlElements = {
 
636
      'datasource': {
 
637
         'BaseClass': GDataSource,
 
638
         'Importable': True,
 
639
         'Description': 'A datasource provides a link to a database table '
 
640
                        'or some similar data store.',
 
641
         'Attributes': {
 
642
            'name':        {
 
643
               'Required': True,
 
644
               'Unique':   True,
 
645
               'Typecast': GTypecast.name,
 
646
               'Description': 'Unique name of the datasource.' },
 
647
            'type':        {
 
648
               'Label': _('Data Object Type'),
 
649
               'Typecast': GTypecast.name,
 
650
               'Default':  "object" },
 
651
            'connection':    {
 
652
               'Label': _('Connection Name'),
 
653
               'Typecast': GTypecast.name,
 
654
               'Description': 'The name of the connection as in '
 
655
                              'connections.conf that points to '
 
656
                              'a valid database.' },
 
657
            'database':    {
 
658
               'Typecast': GTypecast.name,
 
659
               'Deprecated': 'Use {connection} attribute instead' },
 
660
            'table':       {
 
661
               'Label': _('Table Name'),
 
662
               'Typecast': GTypecast.name,
 
663
               'Description': 'The table in the database this datasource '
 
664
                              'will point to.' },
 
665
            'cache':       {
 
666
               'Label': _('Cache Size'),
 
667
               'Description': 'Cache this number of records',
 
668
               'Typecast': GTypecast.whole,
 
669
               'Default':  5 },
 
670
            'prequery':    {
 
671
               'Label': _('Query on Startup'),
 
672
               'Description': 'If true, the datasource is populated on '
 
673
                              'form startup. If false (default), the form '
 
674
                              'starts out with an empty record until the user '
 
675
                              'or a trigger queries the database.',
 
676
               'Typecast': GTypecast.boolean,
 
677
               'Default':  False },
 
678
            'distinct':    {
 
679
               'Typecast': GTypecast.boolean,
 
680
               'Default':  False,
 
681
               'Description': 'TODO' },
 
682
            'order_by':    {
 
683
               'Typecast': GTypecast.text ,
 
684
               'Deprecated': 'Use {sortorder} tag instead' },
 
685
            'master':      {
 
686
               'Label': _('M/D Master DataSource'),
 
687
               'Description': 'If this datasource is the child in a '
 
688
                              'master/detail relationship, this property '
 
689
                              'contains the name of the master datasource.',
 
690
               'Typecast': GTypecast.name },
 
691
            'masterlink':  {
 
692
               'Label': _('M/D Master Field(s)'),
 
693
               'Description': 'If this datasource is the child in a '
 
694
                              'master/detail relationship, this property '
 
695
                              'contains a comma-separated list of the '
 
696
                              'master datasource\'s field(s) used for '
 
697
                              'linking.',
 
698
               'Typecast': GTypecast.text },
 
699
            'detaillink':  {
 
700
               'Label': _('M/D Detail Field(s)'),
 
701
               'Description': 'If this datasource is the child in a '
 
702
                              'master/detail relationship, this property '
 
703
                              'contains a comma-separated list of the '
 
704
                              'this (child\'s) datasource\'s field(s) used '
 
705
                              'for linking.',
 
706
               'Typecast': GTypecast.text },
 
707
            # TODO: Short-term hack
 
708
            'explicitfields': {
 
709
               'Label': _('Explicit Fields'),
 
710
               'Typecast': GTypecast.text,
 
711
               'Description': 'TODO' },
 
712
            'primarykey': {
 
713
               'Label': _('Primary Key Field(s)'),
 
714
               'Description': 'Comma-separated list of the fields that '
 
715
                              'make up the primary key.',
 
716
               'Typecast': GTypecast.text },
 
717
            'primarykeyseq': {
 
718
               'Label': _('Primary Key Sequence'),
 
719
               'Description': 'Name of the sequence used to populate a '
 
720
                              'primary key (only applies to relational '
 
721
                              'backends that support sequences; requires '
 
722
                              'a single {primarykey} value.',
 
723
               'Typecast': GTypecast.text },
 
724
            'requery': {
 
725
               'Label': _('Re-query on commit?'),
 
726
               'Default': True,
 
727
               'Description': 'Requery a record after posting it; requires '
 
728
                              '{primarykey} support and a non-null primary '
 
729
                              'key value at the time of update (whether '
 
730
                              'via a trigger or by the use of '
 
731
                              '{primarykeyseq}.',
 
732
               'Typecast': GTypecast.text }
 
733
               },
 
734
         'ParentTags': None },
 
735
 
 
736
      'sortorder': {
 
737
        'BaseClass': GCSortOrder,
 
738
        'Attributes': {},
 
739
        'ParentTags': ('datasource',),
 
740
      },
 
741
      'sortfield': {
 
742
        'BaseClass': GCSortField,
 
743
        'Attributes': {
 
744
          'name': {
 
745
            'Required': True,
 
746
            'Unique'  : True,
 
747
            'Description': 'The name of the field by which the datasource '
 
748
                           'will be ordered.',
 
749
            'Typecast': GTypecast.name,
 
750
            },
 
751
          'descending': {
 
752
            'Description': 'Selects if the ordering is done in ascending '
 
753
                           '(default) or in descending order.',
 
754
            'Default' : False,
 
755
            'Typecast': GTypecast.boolean,
 
756
          },
 
757
          'ignorecase': {
 
758
            'Default' : False,
 
759
            'Typecast': GTypecast.boolean,
 
760
            'Description': 'Selects wether the ordering is case-sensitive '
 
761
                           'or not.',
 
762
          },
 
763
        },
 
764
        'ParentTags': ('sortorder',),
 
765
      },
 
766
 
 
767
      'staticset': {
 
768
         'BaseClass': GStaticSet,
 
769
#  TODO: This should be replaced by a SingleInstanceInParentObject
 
770
#        instead of SingleInstance (in the whole file)
 
771
#         'SingleInstance': True,
 
772
         'Attributes': {
 
773
            'fields':        {
 
774
               'Typecast': GTypecast.text,
 
775
               'Required': True } },
 
776
         'ParentTags': ('datasource',) },
 
777
      'staticsetrow': {
 
778
         'BaseClass': GStaticSetRow,
 
779
         'ParentTags': ('staticset',) },
 
780
      'staticsetfield': {
 
781
         'BaseClass': GStaticSetField,
 
782
         'Attributes': {
 
783
            'name':        {
 
784
               'Typecast': GTypecast.text,
 
785
               'Required': True },
 
786
            'value':        {
 
787
               'Typecast': GTypecast.text,
 
788
               'Required': True } },
 
789
         'ParentTags': ('staticsetrow',) },
 
790
      'sql': {
 
791
         'BaseClass': GSql,
 
792
         'MixedContent': True,
 
793
         'KeepWhitespace': True,
 
794
         'ParentTags': ('datasource',) },
 
795
      'connection': {
 
796
         'BaseClass': GConnection,
 
797
         'Attributes': {
 
798
            'name': {
 
799
               'Required': True,
 
800
               'Unique': True,
 
801
               'Typecast': GTypecast.name,
 
802
               'Description': 'TODO' },
 
803
            'provider': {
 
804
               'Required': True,
 
805
               'Typecast': GTypecast.name,
 
806
               'Description': 'TODO' },
 
807
            'dbname': {
 
808
               'Required': False,
 
809
               'Typecast': GTypecast.text,
 
810
               'Description': 'TODO' },
 
811
            'service': {
 
812
               'Required': False,
 
813
               'Typecast': GTypecast.text,
 
814
               'Description': 'TODO' },
 
815
            'comment': {
 
816
               'Required': False,
 
817
               'Typecast': GTypecast.text,
 
818
               'Description': 'TODO' },
 
819
            'host': {
 
820
               'Required': False,
 
821
               'Typecast': GTypecast.text,
 
822
               'Description': 'TODO' } },
 
823
         'ParentTags': None,
 
824
         'Description': 'TODO' },
 
825
  }
 
826
 
 
827
  # Add conditional elements
 
828
  xmlElements.update(
 
829
      GConditions.getXMLelements(
 
830
          {'condition':{'ParentTags':('datasource',) } } ))
 
831
 
 
832
  for alteration in updates.keys():
 
833
    xmlElements[alteration].update(updates[alteration])
 
834
 
 
835
  # Connections will have the same parent as datasources
 
836
  xmlElements['connection']['ParentTags'] = xmlElements['datasource']['ParentTags']
 
837
 
 
838
  return xmlElements
 
839
 
 
840
 
 
841
# =============================================================================
 
842
# Classes for implementing sort options
 
843
# =============================================================================
 
844
 
 
845
class GCSortOrder (GObjects.GObj):
 
846
  def __init__ (self, parent = None):
 
847
    GObjects.GObj.__init__ (self, parent, 'GCSortOrder')
 
848
    self.sorting = []
 
849
    self._inits = [self._build]
 
850
 
 
851
  def _build (self):
 
852
    for item in self.findChildrenOfType ('GCSortField'):
 
853
      self.sorting.append ({'name': item.name,
 
854
                            'descending': item.descending,
 
855
                            'ignorecase': item.ignorecase})
 
856
 
 
857
 
 
858
class GCSortField (GObjects.GObj):
 
859
  def __init__ (self, parent = None):
 
860
    GObjects.GObj.__init__ (self, parent, 'GCSortField')
 
861
 
 
862
 
 
863
#
 
864
# Wrapper for standalone DataSources
 
865
# (i.e., not in context of a GObj tree)
 
866
#
 
867
def DataSourceWrapper(connections=None, fields=(), attributes={}, init=True, unicodeMode=False, sql=""):
 
868
  source = _DataSourceWrapper()
 
869
 
 
870
  if sql:
 
871
    s = GSql(source)
 
872
    GContent(s,sql)
 
873
    attributes['type'] = 'sql'
 
874
 
 
875
 
 
876
  if connections:
 
877
    source.setConnectionManager(connections)
 
878
 
 
879
  if init:
 
880
    source.buildAndInitObject(**attributes)
 
881
  else:
 
882
    source.buildObject(**attributes)
 
883
 
 
884
  if fields:
 
885
    source.referenceFields(fields)
 
886
 
 
887
  return source
 
888
 
 
889
 
 
890
class _DataSourceWrapper(GDataSource):
 
891
  def __init__(self, *args, **parms):
 
892
    GDataSource.__init__(self, *args, **parms)
 
893
    self._datasourceDictionary={}
 
894
    self._toplevelParent = self._type
 
895
 
 
896
  def getIntrospector (self):
 
897
    return self._dataObject._connection.introspector
 
898
 
 
899
  def getSchemaCreator (self):
 
900
    return self._dataObject._connection.getSchemaCreator ()
 
901
 
 
902
 
 
903
# =============================================================================
 
904
# Load AppServer specific resources
 
905
# =============================================================================
 
906
 
 
907
class AppServerResourceError (errors.AdminError):
 
908
  pass
 
909
 
 
910
class InvalidURLError (AppServerResourceError):
 
911
  def __init__ (self, url):
 
912
    msg = u_("The URL '%s' is not a valid application server resource "
 
913
             "locator") % url
 
914
    AppServerResourceError.__init__ (self, msg)
 
915
 
 
916
class InvalidResourceTypeError (AppServerResourceError):
 
917
  def __init__ (self, resourceType):
 
918
    msg = u_("Resource type '%s' is not supported") % resourceType
 
919
    AppServerResourceError.__init__ (self, msg)
 
920
 
 
921
class ResourceNotFoundError (AppServerResourceError):
 
922
  def __init__ (self, resType, resName):
 
923
    msg = u_("Resource '%(name)s' of type '%(type)s' not found") \
 
924
          % {'type': resType,
 
925
             'name': resName}
 
926
    AppServerResourceError.__init__ (self, msg)
 
927
 
 
928
# -----------------------------------------------------------------------------
 
929
# Load a resource from appserver and return it as a file-like object
 
930
# -----------------------------------------------------------------------------
 
931
 
 
932
def getAppserverResource (url, paramDict, connections):
 
933
  if url [:12].lower () != 'appserver://':
 
934
    raise InvalidURLError, url
 
935
 
 
936
  if '?' in url:
 
937
    (appUrl, options) = url [12:].split ('?')
 
938
  else:
 
939
    appUrl  = url [12:]
 
940
    options = None
 
941
 
 
942
  parts = appUrl.split ('/')
 
943
  if len (parts) != 3:
 
944
    raise InvalidURLError, url
 
945
  (connection, element, elementName) = parts [:3]
 
946
 
 
947
  if not len (connection) or not len (element) or not len (elementName):
 
948
    raise InvalidURLError, url
 
949
 
 
950
  if not element in ['form']:
 
951
    raise InvalidResourceTypeError, element
 
952
 
 
953
  elementParams = {}
 
954
 
 
955
  if options:
 
956
    for part in options.split (';'):
 
957
      (item, value) = part.split ('=')
 
958
      elementParams [item] = value
 
959
 
 
960
  debugFileName = None
 
961
  if elementParams.has_key ('debug-file'):
 
962
    debugFileName = elementParams ['debug-file']
 
963
    del elementParams ['debug-file']
 
964
 
 
965
  paramDict.update (elementParams)
 
966
 
 
967
  attrs = {'name'    : 'dtsClass',
 
968
           'database': connection,
 
969
           'table'   : 'gnue_class'}
 
970
  fieldList = ['gnue_id', 'gnue_name', 'gnue_module']
 
971
 
 
972
  dts = DataSourceWrapper (
 
973
      connections = connections,
 
974
      attributes  = attrs,
 
975
      fields      = fieldList,
 
976
      unicodeMode = True)
 
977
 
 
978
  parts = elementName.split ('_')
 
979
  if len (parts) != 2:
 
980
    raise ResourceNotFoundError, (element, elementName)
 
981
 
 
982
  (moduleName, className) = parts
 
983
  mc = GConditions.buildConditionFromDict ({'gnue_name': className,
 
984
        'gnue_module.gnue_name': moduleName})
 
985
 
 
986
  rs = dts.createResultSet (mc)
 
987
  if rs.firstRecord ():
 
988
    paramDict ['connection'] = connection
 
989
 
 
990
    res = rs.current.callFunc ("gnue_%s" % element, paramDict)
 
991
 
 
992
    if debugFileName is not None:
 
993
      dfile = open (debugFileName, 'w')
 
994
      dfile.write (res.encode ('utf-8'))
 
995
      dfile.close ()
 
996
 
 
997
    return cStringIO.StringIO (res.encode ('utf-8'))
 
998
 
 
999
  else:
 
1000
    raise ResourceNotFoundError, (element, elementName)
 
1001