commit-gnue
[Top][All Lists]
Advanced

[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)





reply via email to

[Prev in Thread] Current Thread [Next in Thread]