+class ExpenditureExpression(validators.FancyValidator):
+ goodChars = set('1234567890.+-/*() ')
+
+ def _to_python(self, value, state):
+ if (not set(value) <= self.goodChars or
+ re.search(r'([\+\-\*\/])\1', value)):
+ raise formencode.Invalid("Expression contains illegal characters", value, state)
+
+ if value == '':
+ return value, Decimal("0")
+
+ try:
+ number = eval(value)
+ return value, Decimal(str(number))
+ except:
+ raise formencode.Invalid("Not a valid mathematical expression", value, state)
+
+class TagValidator(validators.FancyValidator):
+ def _to_python(self, value,state):
+ try:
+ return set(map(string.strip, value.split(',')))
+ except:
+ raise formencode.Invalid("Unable to parse tags", value, state)
+
+class ShareSchema(Schema):
+ "Validate individual user shares."
+ allow_extra_fields = False
+ user_id = validators.Int(not_empty=True)
+ amount = ExpenditureExpression()
+
+
+def validate_state(value_dict, state, validator):
+ if all(s['amount'] == 0 for s in value_dict['shares']):
+ return {'shares-0.amount': 'Need at least one non-zero share'}
+ValidateNotAllZero = SimpleFormValidator(validate_state)
+
+
+def prune_tags():
+ for tag in meta.Session.query(model.Tag).all():
+ if not tag.expenditures:
+ meta.Session.delete(tag)
+ meta.Session.commit()
+
+class ExpenditureSchema(Schema):
+ "Validate an expenditure."
+ allow_extra_fields = False
+ pre_validators = [NestedVariables()]
+ spender_id = validators.Int(not_empty=True)
+ amount = model.types.CurrencyValidator(not_empty=True)
+ description = validators.UnicodeString(not_empty=True)
+ tags = TagValidator()
+ date = validators.DateConverter()
+ shares = ForEach(ShareSchema)
+ chained_validators = [ValidateNotAllZero]
+
+