[Top][All Lists]
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
[gnue] r9096 - trunk/gnue-forms/src/GFObjects
From: |
reinhard |
Subject: |
[gnue] r9096 - trunk/gnue-forms/src/GFObjects |
Date: |
Thu, 30 Nov 2006 09:50:38 -0600 (CST) |
Author: reinhard
Date: 2006-11-30 09:50:37 -0600 (Thu, 30 Nov 2006)
New Revision: 9096
Modified:
trunk/gnue-forms/src/GFObjects/GFBlock.py
Log:
Docstrings, minor cleanup.
Modified: trunk/gnue-forms/src/GFObjects/GFBlock.py
===================================================================
--- trunk/gnue-forms/src/GFObjects/GFBlock.py 2006-11-30 13:41:19 UTC (rev
9095)
+++ trunk/gnue-forms/src/GFObjects/GFBlock.py 2006-11-30 15:50:37 UTC (rev
9096)
@@ -25,6 +25,8 @@
Classes making up the Block object.
"""
+__all__ = ['GFBlock', 'DatasourceNotFoundError']
+
from gnue.common.datasources import GConditions
from gnue.common.definitions import GParser
from gnue.common import events
@@ -32,25 +34,8 @@
from gnue.forms.GFObjects.GFContainer import GFContainer
from gnue.forms.GFObjects.GFDataSource import GFDataSource
-__all__ = ['GFBlock', 'DatasourceNotFoundError']
-
# =============================================================================
-# Exceptions
-# =============================================================================
-
-class DatasourceNotFoundError(GParser.MarkupError):
- """
- This block references a non-existant datasource.
- """
- def __init__(self, source, block):
- msg = u_(
- "Datasource '%(datasource)s' in block '%(block)s' not found") \
- % {'datasource': source, 'block': block.name}
- GParser.MarkupError.__init__(self, msg, block._url, block._lineNumber)
-
-
-# =============================================================================
# <block>
# =============================================================================
@@ -62,6 +47,8 @@
Blocks can be filled with data by using the L{init_query}, L{copy_query},
and L{execute_query} methods or in a single step with the L{query} method.
+ The L{clear} method populates the block with a single empty record.
+
Within the result set, blocks maintain a pointer to a current record which
can be moved around with the L{first_record}, L{prev_record},
L{next_record}, L{last_record}, L{goto_record}, and L{jump_records}
@@ -75,8 +62,7 @@
L{undelete_record}.
The L{post} and L{requery} methods are available to write the block's
- changes back to the datasource, while the L{clear} method discards all
- changes and replaces the block's contents with a single empty record.
+ changes back to the datasource.
In case the block is connected to an active datasource (especially the GNUe
AppServer), the L{update} method can be used to write the block's changes
@@ -103,37 +89,58 @@
# Constructor
# -------------------------------------------------------------------------
- def __init__(self, parent=None):
+ def __init__(self, parent):
"""
Create a new block instance.
+
+ @param parent: Parent object.
"""
GFContainer.__init__(self, parent, 'GFBlock')
+ # Current mode of the block. Can be 'normal', 'commit' (during commit
+ # triggers), 'init' (during record initialization triggers), or 'query'
+ # (if the form is in query mode).
self.mode = 'normal'
- self.__resultset = None
+ # The underlying resultset.
+ self.__resultset = None
+
+ # The attached data source.
self._dataSourceLink = None # FIXME: make private!
- self._currentRecord = 0 # FIXME: make private!
- self.__record_count = 0
- self._queryDefaults = {} # FIXME: make private!
+ # The current record number.
+ self._currentRecord = 0 # FIXME: make private!
- self.__query_values = {}
+ # The total number of records.
+ self.__record_count = 0
+
+ # The default query criteria, defined through the fields.
+ self._queryDefaults = {} # FIXME: make private!
+
+ # The current query criteria.
+ self.__query_values = {}
+
+ # The criteria of the last query that was executed.
self.__last_query_values = {}
+ # Flag set to True while a query is running.
self.__in_query = False
- #: A list of all GFScrollbar objects bound to this block
+ # A list of all GFScrollbar objects bound to this block
self.__scrollbars = []
- # Populated by GFEntry's initialize
+ # List of all entries bound to this block, populated by GFEntry's
+ # initialize
self._entryList = [] # FIXME: make private!
- # Populated by GFField's initialize
- self._fieldMap = {} # FIXME: make private!
+ # List of all fields bound to this block, populated by GFField's
+ # initialize
self._fieldList = [] # FIXME: make private!
+ # All fields of this block by name.
+ self._fieldMap = {} # FIXME: make private!
+
# Current top of visible portion (only used for scrollbars, every entry
# maintains its own visible portion because the number of visible rows
# can differ per entry)
@@ -155,10 +162,10 @@
'PRE-INSERT': 'Pre-Insert',
'PRE-DELETE': 'Pre-Delete',
'PRE-UPDATE': 'Pre-Update',
+ 'PRE-FOCUSIN': 'Pre-FocusIn',
+ 'POST-FOCUSIN': 'Post-FocusIn',
'PRE-FOCUSOUT': 'Pre-FocusOut',
'POST-FOCUSOUT': 'Post-FocusOut',
- 'PRE-FOCUSIN': 'Pre-FocusIn',
- 'POST-FOCUSIN': 'Post-FocusIn',
'PRE-CHANGE': 'Pre-Change',
'POST-CHANGE': 'Post-Change'}
@@ -239,12 +246,12 @@
# Create a stub/non-bound datasource if we aren't bound to one
if not hasattr(self, 'datasource') or not self.datasource:
- ds = GFDataSource(self._form)
- ds.type = 'unbound'
- self.datasource = ds.name = "__dts_%s" % id(self)
- self._form._datasourceDictionary[ds.name] = ds
- ds._buildObject()
- ds.phaseInit()
+ datasource = GFDataSource(self._form)
+ datasource.type = 'unbound'
+ self.datasource = datasource.name = "__dts_%s" % id(self)
+ self._form._datasourceDictionary[datasource.name] = datasource
+ datasource._buildObject()
+ datasource.phaseInit()
dsDict = self._form._datasourceDictionary
self._dataSourceLink = dsDict.get(self.datasource)
@@ -259,7 +266,6 @@
'dsCursorMoved' : self.__ds_cursor_moved,
'dsRecordInserted' : self.__ds_record_inserted,
'dsRecordLoaded' : self.__ds_record_loaded,
- 'dsRecordTouched' : self.__ds_record_touched,
'dsCommitInsert' : self.__ds_commit_insert,
'dsCommitUpdate' : self.__ds_commit_update,
'dsCommitDelete' : self.__ds_commit_delete})
@@ -267,13 +273,11 @@
# Get min and max child rows, if applicable
self._minChildRows = getattr(self._dataSourceLink, 'detailmin', 0)
self._maxChildRows = getattr(self._dataSourceLink, 'detailmax', None)
+ self.walk(self.__set_child_row_settings)
- self.walk(self.__setChildRowSettings)
-
-
# -------------------------------------------------------------------------
- def __setChildRowSettings(self, child):
+ def __set_child_row_settings(self, child):
# If a child has no rows- or rowSpacer-attribute copy the blocks values
# to the child
@@ -341,7 +345,6 @@
if not self.__in_query:
self._focus_in()
-
# -------------------------------------------------------------------------
def __ds_cursor_moved(self, event):
@@ -354,36 +357,30 @@
if self.__scrolling_blocked:
return
- recno = self.__resultset.getRecordNumber()
self.__current_record_changed(False)
# -------------------------------------------------------------------------
def __ds_record_inserted(self, event):
- self._initializingRecord = event.record
oldmode = self.mode
self.mode = 'init'
+ self.__initializing_record = event.record
self.processTrigger('ON-NEWRECORD')
self.mode = oldmode
+ del self.__initializing_record
# -------------------------------------------------------------------------
def __ds_record_loaded(self, event):
- self._initializingRecord = event.record
oldmode = self.mode
self.mode = 'init'
+ self.__initializing_record = event.record
self.processTrigger('ON-RECORDLOADED')
self.mode = oldmode
+ del self.__initializing_record
# -------------------------------------------------------------------------
- def __ds_record_touched(self, event):
- # This already gets called by GFField??
- # self.__fire_record_trigger('PRE-CHANGE')
- pass
-
- # -------------------------------------------------------------------------
-
def __ds_commit_insert(self, event):
self.__fire_record_trigger('PRE-INSERT')
self.__fire_record_trigger('PRE-COMMIT')
@@ -478,229 +475,207 @@
# -------------------------------------------------------------------------
- # Field access
+ # Queries
# -------------------------------------------------------------------------
- def get_value(self, field, offset):
+ def populate(self):
"""
- Return the value of the given field, depending on the block's state.
+ Populate the block with data at startup.
- @param field: the GFField object.
- @param offset: the offset from the current record (to get data for
- records other than the current one).
+ Depending on the properties of the block, it is populated either with a
+ single empty record or the result of a full query.
"""
- if offset == 0:
- if self.mode == 'query':
- value = self.__query_values.get(field)
+ if self.__get_master_block() is not None:
+ # Population will happen through the master
+ return
- elif self.mode == 'init':
- value = self._initializingRecord[field.field]
+ if self.startup == 'full':
+ self.query()
+ elif self.startup == 'empty':
+ self.clear()
- else:
- if self.__resultset and self.__resultset.current:
- value = self.__resultset.current[field.field]
- else:
- value = None
- else:
- if self.mode in ['query', 'init'] or self.__resultset is None:
- value = None
- else:
- record_number = self.__resultset.getRecordNumber() + offset
- if record_number < 0 or \
- record_number >= self.__resultset.getRecordCount():
- value = None
- else:
- value = self.__resultset[record_number][field.field]
-
- return value
-
# -------------------------------------------------------------------------
- def set_value(self, field, value):
+ def init_query(self):
"""
- Set the value of the given field, depending on the block's state.
+ Set the block into query mode.
- @param field: the GFField object.
+ From this point on, changes to fields within this block will be
+ understood as query criteria. Use L{execute_query} to run the query and
+ populate the block with its result.
"""
- if self.mode == 'query':
- if value is None:
- if field in self.__query_values:
- del self.__query_values[field]
- else:
- self.__query_values[field] = value
+ self.mode = 'query'
+ self.__query_values = {}
+ self.__query_values.update(self._queryDefaults)
+ self.__current_record_changed(True)
+ if self._form.get_focus_block() is self:
+ self.__update_record_status()
- elif self.mode == 'init':
- self._initializingRecord[field.field] = value
+ # -------------------------------------------------------------------------
- else:
- self.processTrigger('Pre-Change')
- field.processTrigger('Pre-Change')
+ def copy_query(self):
+ """
+ Set the block into query mode and initialize the query criteria with
+ the last executed query.
- self.__resultset.current[field.field] = value
- if field.defaultToLast:
- self._lastValues[field.field] = value
+ From this point on, changes to fields within this block will be
+ understood as query criteria. Use L{execute_query} to run the query and
+ populate the block with its result.
+ """
- field.processTrigger('Post-Change')
- self.processTrigger('Post-Change')
+ self.__query_values = {}
+ self.__query_values.update(self.__last_query_values)
+ self.__current_record_changed(True)
+ if self._form.get_focus_block() is self:
+ self.__update_record_status()
- # Status could have changed from clean to modified
- if self._form.get_focus_block() is self:
- self.__update_record_status()
-
- if self.mode != 'init':
- field.value_changed()
-
-
# -------------------------------------------------------------------------
- # Status information
- # -------------------------------------------------------------------------
- def get_record_status(self):
+ def cancel_query(self):
"""
- Find out about the status of the record.
+ Reset the block from query mode back to data browsing mode without
+ executing a query.
- The status can be one of 'empty', 'inserted', 'void', 'clean',
- 'modified', or 'deleted', or C{None} if there is no current record.
+ The result set that was active before switching to query mode remains
+ active.
"""
- if self.__resultset is None:
- return None
+ self.mode = 'normal'
+ self.__current_record_changed(True)
- rec = self.__resultset.current
- if rec is None:
- return None
-
- # try functions that do not depend on detail records first, because
- # they are faster
- if rec.isVoid():
- return 'void'
- elif rec.isDeleted():
- return 'deleted'
- elif rec.isEmpty():
- return 'empty'
- elif rec.isInserted():
- return 'inserted'
- elif rec.isModified():
- return 'modified'
- else:
- return 'clean'
-
# -------------------------------------------------------------------------
- def get_possible_operations(self):
+ def execute_query(self):
"""
- Return a list of possible operations for this block.
+ Execute a query based on query criteria defined through field values.
- The form can use this function to enable or disable commanders (menu
- items or toolbar buttons) that are bound to the respective action.
-
- The return value is basically a list of method names that can be called
- for this block in the current state.
+ The block switches back from query mode to data browsing mode and is
+ populated with the result of the query.
"""
- result = []
+ # Set the maxList to a single master block as a placeholder.
+ maxList = [self.__get_top_master_block()]
- if self.mode == 'query':
- result.append('cancel_query')
- result.append('execute_query')
- else:
- rs = self.__resultset
- if rs is not None:
- rec = self.__resultset.current
- status = self.get_record_status()
+ # Find the longest master/detail chain that contains query values. This
+ # will become the chain that is queried.
+ for block in self._logic._blockList:
+ if block.__query_values.keys():
+ templist = [block]
- if rec is not None:
- if not rs.isFirstRecord():
- result.append('first_record')
- result.append('prev_record')
- if not rs.isLastRecord():
- result.append('next_record')
- result.append('last_record')
- result.append('goto_record')
+ while (templist[-1]).__get_master_block():
+ templist.append((templist[-1]).__get_master_block())
- if not self._form.readonly:
- if self.editable in ('Y', 'new') and status != 'empty':
- result.append('new_record')
- result.append('duplicate_record')
- if self.autoCreate and rs.isLastRecord():
- result.append('next_record')
+ if len(maxList) < len(templist):
+ maxList = templist
- if self.deletable:
- if status not in ('void', 'deleted'):
- result.append('delete_record')
- else:
- result.append('undelete_record')
+ # Store block states
+ for block in self._logic._blockList:
+ block.__last_query_values = {}
+ block.__last_query_values.update(block.__query_values)
- result.append('init_query')
- result.append('copy_query')
+ # Find root block
+ rootBlock = maxList[-1]
- return result
+ # Condition for the master block
+ conditions = rootBlock.__generate_conditional()
- # -------------------------------------------------------------------------
+ # Conditions for the detail block
+ for block in maxList[:-1]:
- def is_saved(self):
- """
- Return True if the block is not pending any uncommited changes.
+ block.processTrigger('PRE-QUERY')
+ for field in block._fieldList:
+ field.processTrigger('PRE-QUERY')
- This method is depreciated. Please use block.is_pending() instead !
- """
+ c = block.__generate_conditional()
+ exist = GConditions.GCexist()
+ exist.table = block._dataSourceLink.table
+ exist.masterlink = block._dataSourceLink.masterlink
+ exist.detaillink = block._dataSourceLink.detaillink
+ exist._children = [c]
+ conditions = GConditions.combineConditions(conditions, exist)
- assert gDebug(1, "DEPRECIATION WARNING: block.isSaved()")
- return not self.is_pending()
+ for block in self._logic._blockList:
+ block.mode = 'normal'
- # -------------------------------------------------------------------------
+ # FIXME: This leaves blocks not affected from this query unupdated
+ self.__in_query = True
+ try:
+ rootBlock._dataSourceLink.createResultSet(conditions)
+ finally:
+ self.__in_query = False
- def is_pending(self):
- """
- Return True if the block is pending any uncommited changes.
- """
+ for block in self._logic._blockList:
+ block.processTrigger('POST-QUERY')
+ for field in block._fieldList:
+ field.processTrigger('POST-QUERY')
- return self.__resultset is not None and self.__resultset.isPending()
-
# -------------------------------------------------------------------------
- def is_empty(self):
+ def query(self, *args, **params):
"""
- Return True if the current record is empty.
+ Execute a query and populate the block with its result.
- Empty means that it has been newly inserted, but neither has any field
- been changed nor has a detail for this record been inserted with a
- status other than empty.
+ @param args: zero, one or more condition trees, can be in dictionary
+ format, in prefix notation, or GCondition object trees. Field names
+ in these conditions are passed directly to the backend, i.e. they
+ are database column names. This is useful to create queries of
+ arbitary complexity.
+ @param params: simple query values in the notation C{fieldname=value}
+ where the fieldname is the name of a GFField. This is useful to
+ create straightforward simple queries where the database columns
+ included in the condition have their GFField assigned. This also
+ works for lookup fields.
+ @returns: True if the query was executed, False if the user aborted
+ when being asked whether or not to save changes.
"""
- return self.__resultset is None or self.__resultset.current is None \
- or self.__resultset.current.isEmpty()
+ # First, convert the fieldname/value pairs to column/value pairs.
+ cond = {}
+ for (fieldname, value) in params.iteritems():
+ field = self._fieldMap[fieldname]
+ if hasattr(field, 'fk_source'):
+ value = field._allowedValuesReverse.get(value)
+ cond[field.field] = value
- # -------------------------------------------------------------------------
+ # Then, mix in the conditions given in args.
+ for arg in args:
+ cond = GConditions.combineConditions(cond, arg)
- def is_first_record(self):
- """
- Returns True if the current record is the first one in the ResultSet
- """
+ # Now, do the query.
+ self._focus_out()
+ if not self._form._must_save():
+ return False
+ self.__in_query = True
+ try:
+ self._dataSourceLink.createResultSet(cond)
+ finally:
+ self.__in_query = False
+ self._focus_in()
+ return True
- return self.__resultset.isFirstRecord()
-
# -------------------------------------------------------------------------
- def is_last_record(self):
+ def clear(self):
"""
- Returns True if the current record is the last one in the ResultSet.
+ Populate the block with a single empty record.
"""
- return self.__resultset.isLastRecord()
+ # Detail blocks cannot be cleared - they follow their master blindly.
+ if self.__get_master_block() is not None:
+ return
+ self._dataSourceLink.createEmptyResultSet()
+
# -------------------------------------------------------------------------
# Record Navigation
# -------------------------------------------------------------------------
def first_record(self):
"""
- Move to the first record of the block. Pre- and Post-Focusout triggers
- are fired before moving as well as Pre- and Post-Focusin triggers after
- moving the record pointer.
+ Move the record pointer to the first record of the block.
"""
if self.mode == 'query':
@@ -719,9 +694,7 @@
def prev_record(self):
"""
- Move to the previous record of the block. Pre- and Post-Focusout
- triggers are fired before moving as well as Pre- and Post-Focusin
- triggers after moving the record pointer.
+ Move the record pointer to the previous record in the block.
"""
if self.mode == 'query':
@@ -740,11 +713,10 @@
def next_record(self):
"""
- Move to the next record. If autoCommit is set a commit is performed
- prior to move the record pointer. If the record is already the last
- one, a new record will be created if the following conditions are met:
- autoCreate is True, there are already records available in the block
- and the block is editable.
+ Move the record pointer to the next record in the block.
+
+ If the record is already the last one, a new record will be created if
+ the "autoCreate" attribute of the block is set.
"""
if self.mode == 'query':
@@ -766,9 +738,7 @@
def last_record(self):
"""
- Move to the last record of the block. Pre- and Post-Focusout triggers
- are fired before moving as well as Pre- and Post-Focusin triggers after
- moving the record pointer.
+ Move the record pointer to the last record of the block.
"""
if self.mode == 'query':
@@ -787,12 +757,10 @@
def goto_record(self, record_number):
"""
- Jump to a specific record. If the block is the block being currently
- edited in the form, the focus-out and focus-in block-level triggers
- will be fired.
+ Move the record pointer to a specific record number in the block.
- @param record_number: record number to jump to. If this is a negative
- value, move relative to the last record
+ @param record_number: Record number to jump to. If this is a negative
+ value, move relative to the last record.
"""
if self.mode == 'query':
@@ -818,7 +786,7 @@
def jump_records(self, count):
"""
- Move the record-pointer by a given adjustment relative to the current
+ Move the record pointer by a given adjustment relative to the current
record.
@param count: the number of records to move from the current record.
@@ -833,230 +801,291 @@
# -------------------------------------------------------------------------
- # Insertion and Deletion of Records
+ # Status information
# -------------------------------------------------------------------------
- def new_record(self):
+ def get_record_status(self):
"""
- Add a new record to the block. If autoCommit is set and a record is
- currently pending it will be commited first.
+ Find out about the status of the record.
+
+ The status can be one of 'empty', 'inserted', 'void', 'clean',
+ 'modified', or 'deleted', or C{None} if there is no current record.
"""
- if self.mode == 'query':
- return
+ if self.__resultset is None:
+ return None
- self._focus_out()
+ rec = self.__resultset.current
+ if rec is None:
+ return None
- self.__resultset.insertRecord(self._lastValues)
+ # try functions that do not depend on detail records first, because
+ # they are faster
+ if rec.isVoid():
+ return 'void'
+ elif rec.isDeleted():
+ return 'deleted'
+ elif rec.isEmpty():
+ return 'empty'
+ elif rec.isInserted():
+ return 'inserted'
+ elif rec.isModified():
+ return 'modified'
+ else:
+ return 'clean'
- self._focus_in()
-
# -------------------------------------------------------------------------
- def duplicate_record(self, exclude=(), include=()):
+ def get_possible_operations(self):
"""
- Create a new record and initialize it with field values from the record
- at the current cursor position.
+ Return a list of possible operations for this block.
- @param exclude: list of fields not to copy.
- @param include: list of fields to copy. An empty list means to copy all
- fields except primary key fields and rowid fields, which are never
- copied anyway.
+ The form can use this function to enable or disable commanders (menu
+ items or toolbar buttons) that are bound to the respective action.
+
+ The return value is basically a list of method names that can be called
+ for this block in the current state.
"""
+ result = []
+
if self.mode == 'query':
- return
+ result.append('cancel_query')
+ result.append('execute_query')
+ else:
+ rs = self.__resultset
+ if rs is not None:
+ rec = self.__resultset.current
+ status = self.get_record_status()
- self._focus_out()
+ if rec is not None:
+ if not rs.isFirstRecord():
+ result.append('first_record')
+ result.append('prev_record')
+ if not rs.isLastRecord():
+ result.append('next_record')
+ result.append('last_record')
+ result.append('goto_record')
- self.__resultset.duplicateRecord(exclude=exclude, include=include)
+ if not self._form.readonly:
+ if self.editable in ('Y', 'new') and status != 'empty':
+ result.append('new_record')
+ result.append('duplicate_record')
+ if self.autoCreate and rs.isLastRecord():
+ result.append('next_record')
- self._focus_in()
+ if self.deletable:
+ if status not in ('void', 'deleted'):
+ result.append('delete_record')
+ else:
+ result.append('undelete_record')
+ result.append('init_query')
+ result.append('copy_query')
+
+ return result
+
# -------------------------------------------------------------------------
- def delete_record(self):
+ def is_saved(self):
"""
- Mark the current record for deletion. The acutal deletion will be done
- on the next commit, call or update.
+ Return True if the block is not pending any uncommited changes.
+
+ This method is depreciated. Please use block.is_pending() instead !
"""
- if self.mode == 'query':
- return
+ assert gDebug(1, "DEPRECATED: <block>.isSaved trigger function")
+ return not self.is_pending()
- self.__resultset.current.delete()
+ # -------------------------------------------------------------------------
- if self._form.get_focus_block() is self:
- self.__update_record_status()
+ def is_pending(self):
+ """
+ Return True if the block is pending any uncommited changes.
+ """
+ return self.__resultset is not None and self.__resultset.isPending()
+
# -------------------------------------------------------------------------
- def undelete_record(self):
+ def is_empty(self):
"""
- Removes the deletion mark from the current record.
+ Return True if the current record is empty.
+
+ Empty means that it has been newly inserted, but neither has any field
+ been changed nor has a detail for this record been inserted with a
+ status other than empty.
"""
- if self.mode == 'query':
- return
+ assert gDebug(1, "DEPRECATED: <block>.isEmpty trigger function")
+ return self.__resultset is None or self.__resultset.current is None \
+ or self.__resultset.current.isEmpty()
- self.__resultset.current.undelete()
+ # -------------------------------------------------------------------------
- if self._form.get_focus_block() is self:
- self.__update_record_status()
+ def is_first_record(self):
+ """
+ Return True if the current record is the first one in the result set.
+ """
+ return self.__resultset.isFirstRecord()
# -------------------------------------------------------------------------
- # Queries
+
+ def is_last_record(self):
+ """
+ Return True if the current record is the last one in the result set.
+ """
+
+ return self.__resultset.isLastRecord()
+
+
# -------------------------------------------------------------------------
+ # Field access
+ # -------------------------------------------------------------------------
- def populate(self):
+ def get_value(self, field, offset):
"""
- Populate the block with data at startup.
+ Return the value of the given field, depending on the block's state.
- Depending on the properties of the block, it is populated either with a
- single empty record or the result of a full query.
+ @param field: the GFField object.
+ @param offset: the offset from the current record (to get data for
+ records other than the current one).
"""
- if self._getMasterBlock() is not None:
- # Population will happen through the master
- return
+ if offset == 0:
+ if self.mode == 'query':
+ value = self.__query_values.get(field)
- if self.startup == 'full':
- self.query()
- elif self.startup == 'empty':
- self.clear()
+ elif self.mode == 'init':
+ value = self.__initializing_record[field.field]
+ else:
+ if self.__resultset and self.__resultset.current:
+ value = self.__resultset.current[field.field]
+ else:
+ value = None
+ else:
+ if self.mode in ['query', 'init'] or self.__resultset is None:
+ value = None
+ else:
+ record_number = self.__resultset.getRecordNumber() + offset
+ if record_number < 0 or \
+ record_number >= self.__resultset.getRecordCount():
+ value = None
+ else:
+ value = self.__resultset[record_number][field.field]
+
+ return value
+
# -------------------------------------------------------------------------
- def init_query(self):
+ def set_value(self, field, value):
+ """
+ Set the value of the given field, depending on the block's state.
- self.mode = 'query'
- self.__query_values = {}
- self.__query_values.update(self._queryDefaults)
- self.__current_record_changed(True)
- if self._form.get_focus_block() is self:
- self.__update_record_status()
+ @param field: the GFField object.
+ """
- # -------------------------------------------------------------------------
+ if self.mode == 'query':
+ if value is None:
+ if field in self.__query_values:
+ del self.__query_values[field]
+ else:
+ self.__query_values[field] = value
- def copy_query(self):
+ elif self.mode == 'init':
+ self.__initializing_record[field.field] = value
- self.__query_values = {}
- self.__query_values.update(self.__last_query_values)
- self.__current_record_changed(True)
- if self._form.get_focus_block() is self:
- self.__update_record_status()
+ else:
+ self.processTrigger('Pre-Change')
+ field.processTrigger('Pre-Change')
- # -------------------------------------------------------------------------
+ self.__resultset.current[field.field] = value
+ if field.defaultToLast:
+ self._lastValues[field.field] = value
- def cancel_query(self):
+ field.processTrigger('Post-Change')
+ self.processTrigger('Post-Change')
- self.mode = 'normal'
- self.__current_record_changed(True)
+ # Status could have changed from clean to modified
+ if self._form.get_focus_block() is self:
+ self.__update_record_status()
- # -------------------------------------------------------------------------
+ if self.mode != 'init':
+ field.value_changed()
- def execute_query(self):
- # Set the maxList to a single master block as a placeholder.
- maxList = [self._getTopMasterBlock()]
+ # -------------------------------------------------------------------------
+ # Insertion and Deletion of Records
+ # -------------------------------------------------------------------------
- # Find the longest master/detail chain that contains query values. This
- # will become the chain that is queried.
- for block in self._logic._blockList:
- if block.__query_values.keys():
- templist = [block]
+ def new_record(self):
+ """
+ Add a new record to the block.
+ """
- while (templist[-1])._getMasterBlock():
- templist.append((templist[-1])._getMasterBlock())
+ if self.mode == 'query':
+ return
- if len(maxList) < len(templist):
- maxList = templist
+ self._focus_out()
- # Store block states
- for block in self._logic._blockList:
- block.__last_query_values = {}
- block.__last_query_values.update(block.__query_values)
+ self.__resultset.insertRecord(self._lastValues)
- # Find root block
- rootBlock = maxList[-1]
+ self._focus_in()
- # Condition for the master block
- conditions = rootBlock._generateConditional()
+ # -------------------------------------------------------------------------
- # Conditions for the detail block
- for block in maxList[:-1]:
+ def duplicate_record(self, exclude=(), include=()):
+ """
+ Create a new record and initialize it with field values from the
+ current record.
- block.processTrigger('PRE-QUERY')
- for field in block._fieldList:
- field.processTrigger('PRE-QUERY')
+ @param exclude: list of fields not to copy.
+ @param include: list of fields to copy. An empty list means to copy all
+ fields except primary key fields and rowid fields, which are never
+ copied anyway.
+ """
- c = block._generateConditional()
- exist = GConditions.GCexist()
- exist.table = block._dataSourceLink.table
- exist.masterlink = block._dataSourceLink.masterlink
- exist.detaillink = block._dataSourceLink.detaillink
- exist._children = [c]
- conditions = GConditions.combineConditions(conditions, exist)
+ if self.mode == 'query':
+ return
- for block in self._logic._blockList:
- block.mode = 'normal'
+ self._focus_out()
- # FIXME: This leaves blocks not affected from this query unupdated
- self.__in_query = True
- try:
- rootBlock._dataSourceLink.createResultSet(conditions)
- finally:
- self.__in_query = False
+ self.__resultset.duplicateRecord(exclude=exclude, include=include)
- for block in self._logic._blockList:
- block.processTrigger('POST-QUERY')
- for field in block._fieldList:
- field.processTrigger('POST-QUERY')
+ self._focus_in()
# -------------------------------------------------------------------------
- def query(self, *args, **params):
+ def delete_record(self):
"""
- Execute a query and populate the block with its result.
+ Mark the current record for deletion. The acutal deletion will be done
+ on the next commit, call or update.
+ """
- @param args: zero, one or more condition trees, can be in dictionary
- format, in prefix notation, or GCondition object trees. Field names
- in these conditions are passed directly to the backend, i.e. they
- are database column names. This is useful to create queries of
- arbitary complexity.
- @param params: simple query values in the notation C{fieldname=value}
- where the fieldname is the name of a GFField. This is useful to
- create straightforward simple queries where the database columns
- included in the condition have their GFField assigned. This also
- works for lookup fields.
- @returns: True if the query was executed, False if the user aborted
- when being asked whether or not to save changes.
+ if self.mode == 'query':
+ return
+
+ self.__resultset.current.delete()
+
+ if self._form.get_focus_block() is self:
+ self.__update_record_status()
+
+ # -------------------------------------------------------------------------
+
+ def undelete_record(self):
"""
+ Remove the deletion mark from the current record.
+ """
- # First, convert the fieldname/value pairs to column/value pairs.
- cond = {}
- for (fieldname, value) in params.iteritems():
- field = self._fieldMap[fieldname]
- if hasattr(field, 'fk_source'):
- value = field._allowedValuesReverse.get(value)
- cond[field.field] = value
+ if self.mode == 'query':
+ return
- # Then, mix in the conditions given in args.
- for arg in args:
- cond = GConditions.combineConditions(cond, arg)
+ self.__resultset.current.undelete()
- # Now, do the query.
- self._focus_out()
- if not self._form._must_save():
- return False
- self.__in_query = True
- try:
- self._dataSourceLink.createResultSet(cond)
- finally:
- self.__in_query = False
- self._focus_in()
- return True
+ if self._form.get_focus_block() is self:
+ self.__update_record_status()
# -------------------------------------------------------------------------
@@ -1065,13 +1094,16 @@
def post(self):
"""
- Post all pending changes of the block
+ Post all pending changes of the block and all its detail blocks to the
+ database backend.
+
+ If this function has been run successfully, L{requery} must be called.
"""
assert gDebug(4, "processing commit on block %s" % self.name, 1)
try:
- if self._getMasterBlock() is None:
+ if self.__get_master_block() is None:
self._dataSourceLink.postAll()
except:
# if an exception happened, the record pointer keeps sticking at
@@ -1083,32 +1115,34 @@
def requery(self, commit):
"""
- Called after the commit on the backend is through.
- """
+ Finalize storing the values of the block in the database.
- if self._getMasterBlock() is None:
- self._dataSourceLink.requeryAll(commit)
+ This method must be called after L{post} has run successfully. It
+ restores the block into a consistent state.
- # -------------------------------------------------------------------------
-
- def clear(self):
+ @param commit: True if a commit has been run on the backend connection
+ between post and requery.
"""
- Clear the result set of this block.
- """
- # Detail blocks cannot be cleared - they follow their master blindly.
- if self._getMasterBlock() is not None:
- return
+ if self.__get_master_block() is None:
+ self._dataSourceLink.requeryAll(commit)
- self._dataSourceLink.createEmptyResultSet()
-
# -------------------------------------------------------------------------
# Function and Update
# -------------------------------------------------------------------------
def call(self, name, parameters):
+ """
+ Call a server side function.
+ Currently, the only backend to support server side function calls is
+ gnue-appserver.
+
+ @param name: Function name.
+ @param parameters: Function parameter dictionary.
+ """
+
# Remember the current record; the record pointer is not reliable
# between postAll and requeryAll!
current = self.__resultset.current
@@ -1124,7 +1158,14 @@
# -------------------------------------------------------------------------
def update(self):
+ """
+ Update the backend with changes to this block without finally
+ commmitting them.
+ This can be useful to make the backend react to changes entered by the
+ user, for example to make gnue-appserver recalculate calculated fields.
+ """
+
self._dataSourceLink.postAll()
self._dataSourceLink.requeryAll(False)
@@ -1138,6 +1179,7 @@
Return the current ResultSet of the block.
"""
+ gDebug(1, "DEPRECATED: <block>.getResultSet trigger function")
return self.__resultset
# -------------------------------------------------------------------------
@@ -1364,13 +1406,13 @@
# Return the top level master block of this block
# -------------------------------------------------------------------------
- def _getTopMasterBlock(self):
+ def __get_top_master_block(self):
result = self
- master = result._getMasterBlock()
+ master = result.__get_master_block()
while master is not None:
result = master
- master = result._getMasterBlock()
+ master = result.__get_master_block()
return result
@@ -1378,7 +1420,7 @@
# Return the master block of this block
# -------------------------------------------------------------------------
- def _getMasterBlock(self):
+ def __get_master_block(self):
if self._dataSourceLink.hasMaster():
ds = self._dataSourceLink.getMaster()
@@ -1396,7 +1438,7 @@
# Create a condition tree
# -------------------------------------------------------------------------
- def _generateConditional(self):
+ def __generate_conditional(self):
"""
Create a condition tree based upon the values currently stored in the
form.
@@ -1529,3 +1571,21 @@
value = result
return value
+
+
+# =============================================================================
+# Exceptions
+# =============================================================================
+
+class DatasourceNotFoundError(GParser.MarkupError):
+ """
+ This block references a non-existant datasource.
+ """
+ def __init__(self, source, block):
+ GParser.MarkupError.__init__(self, u_(
+ "Datasource '%(datasource)s' in block '%(block)s' not "
+ "found"
+ ) % {
+ 'datasource': source,
+ 'block': block.name},
+ block._url, block._lineNumber)
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [gnue] r9096 - trunk/gnue-forms/src/GFObjects,
reinhard <=