Find file
Fetching contributors…
Cannot retrieve contributors at this time
196 lines (164 sloc) 6.7 KB
# Copyright 2012 Hai Thanh Nguyen
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# See the License for the specific language governing permissions and
# limitations under the License.
import hashlib, inspect, cgi
from lxml.html.clean import Cleaner
from google.appengine.ext import ndb
import validators
class UnsavedProperty(object):
_value = None
def __init__(self, verbose_name):
self._verbose_name = verbose_name
def __get__(self, entity, type=None):
if entity is None: #called on class
return self
return self._value
def __set__(self, entity, value):
self._value = value
class PasswordProperty(ndb.StringProperty):
def do_hash(str):
m = hashlib.sha256()
return m.hexdigest()
def _to_base_type(self, value):
return self.do_hash(value)
class BooleanProperty(ndb.BooleanProperty):
def _validate(self, value):
if value in [u"yes", "yes"]:
value = True
elif value in [u"no", "no"]:
value = False
assert type(value) == bool
return value
class FormIntegerProperty(ndb.IntegerProperty):
"""Like ndb.IntegerProperty but doesn't raise exception when the value is a wrong string, and this property
automatically converts valid integer strings to integer."""
def _validate(self, value):
return int(value)
except ValueError:
def _to_base_type(self, value):
return int(value)
class EscapedHtmlProperty(ndb.TextProperty):
def _to_base_type(self, value):
return cgi.escape(value)
class FilteredHtmlProperty(ndb.TextProperty):
def _validate(self, value):
return value.strip()
def _to_base_type(self, value):
value = value.strip()
return Cleaner(add_nofollow=True).clean_html(value) if value else ""
class AuthorProperty(ndb.KeyProperty):
def __get__(self, entity, unused_cls=None):
if entity is None:
return self
key = self._get_value(entity)
if key:
model = key.get()
return model.display_name or model.username
return None
class MyMetaModel(ndb.MetaModel):
def __init__(cls, name, bases, classdict):
super(MyMetaModel, cls).__init__(name, bases, classdict)
#Add the UnsavedProperty's to the internal _unsaved_properties for use with get_verbose_name
cls._unsaved_properties = {}
for name in set(dir(cls)):
attr = getattr(cls, name, None)
if isinstance(attr, UnsavedProperty):
cls._unsaved_properties[name] = attr
class ValidationEngine(object):
def __init__(self, model):
self.errors = {}
self.model = model
self._additional_inf = {
"unique": ["model", "field"],
self._inf = {}
self.set_inf("model", self.model)
def set_inf(self, name, val):
self._inf[name] = val
def get_inf(self, name):
return self._inf[name]
"""Validate a form field with the validators in the validators module, errors are saved into
the self.errors dictionary"""
def validate(self, field, method, *args):
self.set_inf("field", field)
field_value = getattr(self.model, field)
#Do not perform validation if:
# Field is None (not used in the form)
# Field is left blank and this is not a "required" validation
if (field_value is not None) and ((method == "required") or (field_value != "")):
fn = getattr(validators, "validate_"+method)
additional = {}
if method in self._additional_inf:
for inf in self._additional_inf[method]:
additional[inf] = self.get_inf(inf)
fn(field_value, *args, **additional)
except validators.ValidationError as e:
if not(field in self.errors):
self.errors[field] = []
def has_error(self):
return bool(self.errors)
class FormModel(ndb.Model):
__metaclass__ = MyMetaModel
_validated = False
def __init__(self, *args, **kwds):
super(FormModel, self).__init__(*args, **kwds)
self.validations = ValidationEngine(self)
def get_errors(self):
return self.validations.errors
def _validation(self):
"""This method is for child classes to override and define the validations"""
return {}
def validate(self):
field_dict = {}
for cls in inspect.getmro(type(self)):
if hasattr(cls, "_validation"):
else: break
for field, v_dict in field_dict.iteritems():
for method, params in v_dict.iteritems():
if not isinstance(params, tuple):
raise Exception("Validation parameters must be a python tuple, check your model's _validation() method")
self.validations.validate(field, method, *params)
self._validated = True
return not self.validations.has_error()
def put(self, force_validation=True):
if force_validation:
if not self._validated:
if self.get_errors():
raise Exception("Cannot save to the database because there are validation errors.")
return super(FormModel, self).put()
def assign(self, rhandler):
data = rhandler.request.POST.dict_of_lists()
for field, vlist in data.iteritems():
if hasattr(self, field): #Model has the attribute named the same as the POST field name
setattr(self, field, vlist[0] if len(vlist)<2 else vlist)
def get_verbose_name(self, attr):
if attr in self._properties:
ret = self._properties[attr]._verbose_name
elif attr in self._unsaved_properties:
ret = self._unsaved_properties[attr]._verbose_name
raise AttributeError("FormModel instance has no attribute %s" % attr)
return ret if ret != None else attr
def is_required(self, field):
v_dict = self._validation()
if (field in v_dict) and ("required" in v_dict[field]):
return True
else: return False