Hi all,

I have been working on this idea over the last couple days which I think
would be cool/handy to have in QGIS and I would be interested to get some
feedback with what people think.

The general idea is to add a form of field validation to the built-in
attribute forms.  The use case for this is pretty clear I think, being able
to let the user/admin of any layer define validation rules to check against
the data entered on the attribute form when the user presses OK providing
instant feedback.  I normally do this on my PostGIS tables but you don't
get errors until you try and commit the data and even then it's a simple
"there is a error", if you have added more then 1 item you have to check
each one to work out what is wrong, and this method only applies to a
PostGIS layer.  The idea I have been drafting would work on any layer, as
it's at the QGIS level vs at the datasource level.

My idea is to use simple a simple Python file with basic functions that
will be called for each field in the attribute form that is editable.  I'm
a big fan of convention over configuration so my idea uses conventions to
define what functions are used to validate what fields.  The function will
return False and an error message that can be shown to the user if
something is wrong.

The functions use the following syntax: validate{FieldName} or
validate{FieldIndex} for naming (plus some other styles) e.g

def validateName():
   if value == "":
      return (False,"Name can't be NULL")
   return True

You would place the function in a python file e.g PeopleRules.py and then
tell QGIS to use this file to look for validation rules, pretty much the
same as we do now for having custom Python forms.

Overview of logic:

   1. Define Python file with rules
   2. Tell QGIS to use the rules file for the layer.
   3. Open attribute form
   4. Edit some values
   5. Press OK
   6. QGIS calls each function in the rules file to validate each field
   that is defined with a rule
   7. If there are errors: cancel the accept() action and show the errors.
    If there are no errors go though like normal.

My thoughts for using Python are:

   - Easy to read and write
   - Not limited to what we have in QgsExpression and easier to expand.
   - Can add documentation to validation rules.
   - Can add more complex validation rules e.g. regex, parsing, database
   checks etc

My overall goal with this is to reduce the need to create custom Python Qt
forms when doing data entry, and doing validation is one of the reasons I
tend to make custom Python Qt forms but then you loose the ease of QGIS
doing most of the form work for you.

I have attached two files which have a example of what the idea is.
 fieldChecks.py is an example rules file with comments to explain each
section, runMe.py has the logic for reading and executing the rules for
each field.  Run: python runMe.py to see the running logic and output.

What love to get peoples thoughts about this, users and devs a like.

- Nathan
#!/usr/bin/env python

''' Validation rules are listed here and called from the qgis.validation module as needed.
Each rule follows a convention based naming style in order to picked up as a validation
rule.

Syntax is validate{FieldName} or validate{ID} the validation module will auto resolve the
mehtod from the field name or index

You can also have a different name to the field being validated and use a doc string
to tell qgis.validation what field it validates. Add Validate:{Name} or Validate:{ID}
to the __doc__ string, see validatePersonIsNotBruce() as example
'''

def validateY():
    if value > attributes["x"]:
        return True
    return (False,"Y must be greater then X")

def validateName():
    ''' This method will be called when the name field is needed to be validated
        Function naming convention is validate{FieldName} or validate{FieldNo}
    '''
    if not value == "":
        return True
    return (False,"Name must not be null")

def validate1():
    ''' This method will be called when the field no 1 is needed to be validated
    '''
    if value == "Hello World":
        return True
    return False

def validatePersonIsNotBruce():
    '''
        Validate:Person
        The Validate:{FieldName or ID} can be used to name the function different
        to what is getting validated.
        Function name must still start with "validate"
    '''
    if value == "Bruce":
        return (False,"Person must not be 'Bruce'")
    return True


#!/usr/bin/env python
import re

class QgsAttributeDialog:
    def accept(self):
        first = {"FirstField":"Hello",
                    "Name":"Joe",
                    "Y":1234,
                    "x":12,
                    'Person':'Bruce'
                    }

        second = {"FirstField":"",
                    "Name":"",
                    "Y":122134,
                    "x":12132151232,
                    'Person':'Joe'
                    }

        third = {"FirstField":"",
                    "Name":"Bill",
                    "Y":122134,
                    "x":12,
                    'Person':'Joe'
                    }

        self.validateFields(first)
        print "-- Report the errors to the users"
        print "-- User fixes mistakes and clicks OK again"
        self.validateFields(second)
        print "-- Report the errors to the users"
        print "-- User finally fixes mistakes and clicks OK again"
        self.validateFields(third)

    def validateFields(self, attributeMap):
        ''' This method would live in a qgis.validation module in the python folder
        and be called by QgsAttributeDialog::accept() when needed
        '''
        # fieldChecks is the module containing our rules in this case.
        import fieldChecks
        forValidation = {}
        fieldChecks.attributes = attributeMap
        # Get all the validation functions that are using docstrings
        for name, func in fieldChecks.__dict__.iteritems():
            if "validate" in name:
                docString = func.__doc__
                if docString is None:
                    continue

                try:
                    fieldId = re.findall("Validate:(.*?)\s",docString)[0]
                    if not fieldId == '':
                        forValidation["validate%s" % fieldId] = func
                except IndexError:
                    continue

        for name,value in attributeMap.iteritems():
            #Build a validate{Name} or validate{ID} keyword.
            #search the __dict__ first then search the forValidation dict next
            validateNameKey = "validate%s" % name
            func = fieldChecks.__dict__.get(validateNameKey)
            if func is None:
                func = forValidation.get(validateNameKey)

            if func is None:
                continue

            fieldChecks.value = value

            result = func()

            if type(result) is tuple:
                print name,":", result[1]

if __name__ == '__main__':
    dialog = QgsAttributeDialog()
    dialog.accept()
_______________________________________________
Qgis-developer mailing list
Qgis-developer@lists.osgeo.org
http://lists.osgeo.org/mailman/listinfo/qgis-developer

Reply via email to