Package: fforms
fformsis a Python HTML form validation library that allows you to
take control of the validation and rendering while minimizing
boilerplate and awkward constructions.fformsdoes not provide any
widgets or rendering assistance beyond ease of access to form values
and validation errors. It does, however, let you define and use custom
validators with extreme ease. Read on to see how!Installationfformsis a pure Python package with no dependencies, so
installing it should be very straightforward. It’s tested on Python
3.3 through 3.6.[1]Option A:pip install fformsOption B: download the source code, change into the working
directory, and runpython setup.py installFeaturesA familiar interface for use in viewsform=fforms.bind_dotted(registration_form,request.POST,request.FILES)ifrequest.method=='POST':ifform.is_valid():data=form.clean_data# do stuff with the datareturnredirect("/url/to/redirect")returnrender_template("template-name.html",form=form)Logical data structures that allow easy access from templates{%setfield=form.username%}<inputtype="text"{#
    #}value="{{field.raw_data|default("",True)}}"{#
    #}{%iffield.error%}class="invalid"{%endif%}{#
    #}name="field.full_name">{%iffield.error%}<spanclass="error-msg">{{field.error}}</span>{%endif%}<ul>{%forfieldinform.attendees%}<inputtype="text"name="{{field.full_name}}">{%endfor%}</ul>Easy to add custom validatorsmyform['product_id'].validator=validators.from_regex("^[A-Z]{2}-[0-9]{3}-[0-9]{4}$""Please provide a valid product ID in the format XX-NNN-NNNN")myform['state'].validator=validators.one_of("ME","NH","VT",msg="We only serve the Northeast")deffunky_validator(data):left,right=data[:4],data[4:]if(int(left,16),right)notinVALUES:raiseValidationError("Invalid format",data)# more funky testsreturnprocess(left,right)# Set the field valuemyform['funky-field'].validator=funky_validatorInternationalization using your choice of framework.deftranslate_msg(msg,kwargs):# msg is a str.format-formatted string, and kwargs has all the# field values.msg=_(msg)kwargs={key:_(val)forkey,valinkwargs.items()}returnmsg.format(**kwargs)fforms.validators.DeferredMessage.process_message=translate_msgPure Python with no outside dependencies. Don’t bring in a gigantic
web framework just to use their forms libraryLicenseThis project is licensed under the MIT license.Getting startedTo usefforms, you’ll first need to define a schema for your
form. This is most easily accomplished by usingfforms.schema.make_from_literalas followsfromfformsimportmake_from_literal,validators,bind_dottedschema=make_from_literal({'username':validators.from_regex("^[a-zA-Z][a-zA-Z0-9_]{0,25}"),'password':validators.chain(validators.limit_length(min=8,max=128),validators.from_regex("[a-z]","{field.name}must contain lowercase letters"),validators.from_regex("[A-Z]","{field.name}must contain uppercase letters"),validators.from_regex("[0-9]","{field.name}must contain numbers"),validators.from_regex("[^a-zA-Z0-9]","{field.name}must contain special characters")),'password2':validators.ensure_str,'email':validators.email,'address':{'street':validators.chain(validators.ensure_str,validators.limit_length(min=2)),'street2':validators.ensure_str,'zip_code':validators.chain(validators.from_regex("^[0-9]+$"),validators.limit_length(min=5,max=5)),'state':validators.one_of("ME","NH","VT","MA")},'tags':[{'name':validators.ensure_str}],})schema.validator=validators.chain(schema.validator,# The default is validators.all_childrenvalidators.key_matcher("password","password2","Please ensure the two passwords match"))schema['tags'].validator=validators.chain(schema['tags'].validator,validators.limit_length(min=1,max=8))Once you have a schema object, you can bind it to data to create a
bound form objectform=bind_dotted(schema,{'username':'felipeochoa','password':'123abcDEF!@#','password2':'123ABCdef!@#','email':'me@example','address.street':'123 Main St.','address.street2':'Unit 1','address.zip code':'1234','tags:0.name':'tag1','tags:1.name':'tag2',})assertnotform.is_valid()forfieldinform:print("%s%r(%s)"%(field.name,field.clean_data,field.error))Which will print out:username 'felipeochoa' (None)
password '123abcDEF!@#' (None)
password2 '123ABCdef!@#' (None)
tags [{'name': 'tag1'}, {'name': 'tag2'}] (None)
address None ()
email 'me@example' (Enter a valid email address.)(address does not have an error message of its own; all the errors are
in its children).You can use this code in your views or templates in a conventient
fashiondefmy_view(request):form=bind_dotted(registration_form,request.POST,request.FILES)ifrequest.method=='POST':ifform.is_valid():data=form.clean_data# do stuff with the datareturnredirect("/url/to/redirect")returnrender_template("template",form=form){%ifform.error%}<spanclass="error-msg">form.error</span>{%endif%}{# more stuff #}{%forfieldinform.tags%}<inputtype="text"value="{{field.value}}"name="{{field.full_name}}">{%endfor%}Detailed Documentationfformsoperates on three basic concepts: Schema, Validators, and
Forms.SchemaThink of a schema like an unbound form. It contains the blueprint for
bound forms: field names, definitions, and validators. Schema form
trees that describe the form you are validating, so some schema can
have child schema that perform validation/conversion on a part of the
received data. Schema can be one of the following typesMapSchemaLike a Python dictionary, mapping names to
sub-schemaSequenceSchemaA variable length list where all sub-schema are
of the same kind.LeafSchemaDoes not contain any children of its own.All three types of schema support their own validation, in addition to
any validation that their children might perform. E.g., if you have a
schema defined asuser_schema={'username':limit_length(max=25),'password':ensure_complexity(numbers=True,uppercase=True),'password2':ensure_str,}You can add a higher-level validatorkey_matcher('password','password2')that additionally verifies that the two values
match. You could then compose that schema into another one, e.g.many_users_schema=[{'username':limit_length(max=25),'password':ensure_complexity(numbers=True,uppercase=True),'password2':ensure_str,}]This new schema would accept a list ofusername/password/password2combinations, which would be useful if you had to create
multiple users at the same time. You could then add a new validator
to, say, ensure no more than 5 users are created at a timelimit_length(max=5).ValidatorsWe’ve seen a few validators already, but haven’t yet defined what they
are or what they do. Mechanically, validators are simply functions of
one value that produce another value. The argument is the value
attached to the field that the validator must check and/or
transform. For example, theas_intvalidator is defined as followsdefas_int(data):"Extract an integer from the data."try:returnint(data)except(TypeError,ValueError):raiseValidationError("{field.name}must be a whole number",data)Each schema can have two validatorsValidator: The validator stored underschema.validatorwill
be called after theform.is_validmethod is called and will takefield.raw_dataas its input. The output from this validator will
be stored asfield.clean_data. If the validator raisesValidationError, the message from the error will be extracted
and set asfield.error, andfield.clean_datawill be left asNone. For schema with children, validators typically pass
through the data unmodified, leaving all conversion to leaf
nodes. In principle though, parent validators could modify the
values generated by the child validators;parent validators are
run after child validators. Though schema only have one validator,
the nature of validators means that multiple validators can be easily
composed into a bigger one.Pre-processor: The validator stored underschema.pre_processorwill take the raw data provided to the
field and transform it into a (still serialized) value. The return
value of the validator will be saved asfield.raw_data. Pre-processing validators are called as soon as
the field is bound, so should not do much validation work. In
particular, the return value should still be “raw” (i.e.,
serialized), sincefield.raw_datawill be used directly in
templates. Most applications should have no need to override the
default pre-processor, which is a no-op. A notable exception is to
provide a serialized value for any files passed in viarequest.FILESor similar.Forms/FieldsForms (technicallyBoundFieldobjects) marry theSchemawith
data to be validated. Forms are typically created by callingSchema.bindor another helper function likebind_to_dotted. Forms provide a nice API for accessing errors and
cleaned data. Like schema, forms are nodes in a tree structure (that
mirrors the schema they were built for).Once you have a form object, calling itsis_validmethod will
returnTrueif validation succeeded for the schema andFalseif it didn’t. You will then be able to access the fields’.errorand.clean_dataarguments. The.errorattribute stores a
string with the error message provided by the validator that rejected
the data. The.clean_datastores a de-serialized value that can
by passed to other parts of your application.[1]On Python 3.3 on Windows, the email validator won’t work
with addresses using IPv6 domain names.