An XForm is composed of one or more ordered fields. Currently four types are supported for each field, integer, decimal, string and coordinate. Each field must have a unique human readable name, as well as a shorter slug used to uniquely identify it. You may also specify help text that will be displayed in rich XForm clients.
An integer field which contains even numbers.
Examples:
survey +age 20
survey +height 35
A decimal field holds a real, non integer number. It has a maximum precision of 9 digits before and 9 digits after the period.
Examples:
survey +flow 5.4
survey +temp 98.6
A string field, represents just a block of text.
Examples:
survey +name matt berg
survey +comment well is in need of repair
A coordinate field, which in practice is a pairing of decimal fields.
Examples:
survey +loc 1.4564 1.5435
survey +track 1.5456 1.2355
You can also create custom field types, which can point either to your own domain objects or which just do specialized parsing before storing the value as a primitive.
You can register fields using the register_field_type static method in XFormField. The Coordinate (geopoint) field is actually implemented in this way, using the following code:
def create_geopoint(command, value):
"""
Used by our arbitrary field saving / lookup. This takes the command and the string value representing
a geolocation and returns a Point location.
"""
coords = value.split(' ')
if len(coords) < 2:
raise ValidationError("+%s parameter must be GPS coordinates in the format 'lat long'" % command)
for coord in coords[0:2]:
try:
test = float(coord)
except ValueError:
raise ValidationError("+%s parameter must be GPS coordinates the format 'lat long'" % command)
# lat needs to be between -90 and 90
if float(coords[0]) < -90 or float(coords[0]) > 90:
raise ValidationError("+%s parameter has invalid latitude, must be between -90 and 90" % command)
# lng needs to be between -180 and 180
if float(coords[1]) < -180 or float(coords[1]) > 180:
raise ValidationError("+%s parameter has invalid longitude, must be between -180 and 180" % command)
# our cleaned value is the coordinates as a tuple
cleaned_value = Point.objects.create(latitude=coords[0], longitude=coords[1])
return cleaned_value
# register geopoints as a type
XFormField.register_field_type(XFormField.TYPE_GEOPOINT, 'GPS Coordinate', create_geopoint,
xforms_type='geopoint', db_type=XFormField.TYPE_OBJECT)
Alternatively, you could create a new field type to deal with custom formats. The following is an example of a custom ‘timespan’ field which parses time values in formats like ‘1day’ or ‘6 months’, but stores the value in an ordinary integer field representing the number of days:
def parse_timespan(command, value):
"""
Parses a timespan object in the format '5days' or '6 months', returning the value as an integer
of the number of days represented by that timespan.
"""
match = re.match("(\d+)\W*months?", value, re.IGNORECASE)
if match:
return int(match.group(1))*30
match = re.match("(\d+)\W*days?", value, re.IGNORECASE)
if match:
return int(match.group(1))
raise ValidationError("%s parameter value of '%s' is not a valid timespan." % (command, value))
XFormField.register_field_type('timespan', 'Timespan', parse_timespan,
xforms_type='string', db_type=XFormField.TYPE_INT)
Every field can have one or more ordered constraints applied to it. For each constraint you can specify a custom error message that is returned if the constraint fails.
The types of constraints are:
Some example regular expressions:
# only matches the strings 'mal', 'fev' or 'shi'
^(mal|fev|shi)$
# matches phone numbers in the form 333-3333
^\d\d\d-\d\d\d\d$
# forces the string to be only lowercase letters
^[a-z]+$