# coding=utf-8
from inspect import isclass
from flyforms.validators import ValidationError
from flyforms.common import UNSET
from flyforms.fields import Field
from flyforms.compatibility import with_metaclass
__all__ = ("Form", "validate_schema")
class FormMeta(type):
"""
The metaclass for Form and it's subclasses
"""
def __new__(mcs, name, bases, dct):
fields = [] # create a container for Form's fields names
for attr, val in dct.items(): # walk through class attributes
if isinstance(val, Field): # find all Field instances
fields.append(attr) # catch them
dct[attr] = FormField(attr, val) # and replace with descriptor
# Update class attributes
dct["_fields"] = set(fields)
cls = super(FormMeta, mcs).__new__(mcs, name, bases, dct) # create new class
# Get all fields names from MRO
_fields = set() # prepare container for Form fields names
for base in cls.__mro__: # walk through the MRO
_fields |= getattr(base, "_fields", set()) # get fields names from each base
cls._fields = _fields # update Form's fields
return cls
class FormField(object):
def __init__(self, name, field_obj):
if not isinstance(field_obj, Field):
raise TypeError("You should bind FormField with Field subclass instance, not {}".format(type(field_obj)))
self.name = name
self.field = field_obj
def __get__(self, instance, owner):
if isinstance(instance, Form) and issubclass(owner, Form):
return instance.raw_data.get(self.name, self.field.default)
if instance is None and issubclass(owner, Form):
return self.field
raise AttributeError("You can\'t use FormField without Form")
def __set__(self, instance, value):
if not isinstance(instance, Form):
raise AttributeError("You can\'t use FormField without Form")
if instance.raw_data.get(self.name, UNSET) is not UNSET:
raise AttributeError("You can\'t overwrite already bound field {}!".format(self.name))
try:
value = self.field.bind(value)
except ValidationError as e:
instance.errors[self.name] = str(e)
value = UNSET
instance.raw_data[self.name] = value
def __delete__(self, instance):
raise AttributeError("You can\'t delete Form fields!")
[docs]def validate_schema(form_cls, **data):
"""
This function validates given data via given :py:class:`.Form` subclass
without defined Form instantiation.
:param form_cls: user-defined Form
:type form_cls: :py:class:`.Form`
:param data: data to validate
:return: boolean flag is data valid for given :code:`form_cls`
:raises TypeError: if given :code:`form_cls` is not class
or not :py:class:`.Form` subclass
"""
if not isclass(form_cls) or not issubclass(form_cls, Form):
raise TypeError("You should pass Form subclass, not instance as first arg")
for field_name in form_cls._fields:
field_obj = getattr(form_cls, field_name)
if not field_obj.is_valid(data.pop(field_name, UNSET)):
return False
if data:
return False
return True