HTML forms data validation, conversion & transformation
Project description
formv is a configurable Python library that can be used to validate, convert & transform HTML forms data.
Tested under Python 2.7 and 3.2.
Includes validators for:
- basic types boolean, strings, numbers, dates, time, ranges, lists, sets - chained types pairs, multiple fields, post-codes, states, currencies, languages, phone numbers, credit cards - compound types any validator, piped validators - signers & encoders cost-based PBKDF2 encoding used to sign strings (e.g. cookies), encode strings (e.g. user, password) and sign serialized objects (e.g. serialized sessions stored on disk) - documents (.pdf, .txt, .csv, .doc, etc.) can be validated (size, mime-type), stored, backed-up, compressed, reused - images (.jpg, .png, .gif, etc.) can be validated (size, mime-type), stored, backed-up, resized, watermarked, imprinted, reused - geographic data based on extendable YAML configuration files or user-defined callables: countries, country-codes, states, various naming styles, currencies, languages, post-codes, latitude, longitude, geo-distance calculation - network related IPv4, IPv6, CIDR, MAC - web-forms (schema validation) simple fields, chained fields, key-based dependency
Example:
#------------------------------------------------------------------------ # schema validation including file upload, transformation and recovery #------------------------------------------------------------------------ import os, formv from formv.configuration import build from formv.validators import * from formv.exception import Invalid from formv.utils import dict_object from formv.utils.compat import PY2 from formv.utils.fileinfo import BaseFileInfo, ReusedFileInfo from tests import multipart from examples import to_str, setup_mime_types, make_environ as env from datetime import datetime from mimetypes import guess_type from webob import Request, Response app_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # ---------------------------------------------------------------- # configuration is required only if you use validators based on # geographic data: countries, currencies, languages, post-codes, # latitude, longitude, geo-distance calculation,... formv.config = build(config_root=os.path.join(app_root, 'tests')) # ---------------------------------------------------------------- mime_types = setup_mime_types() session = dict_object(files=dict_object()) class WebForm(VSchema): fields = { 'first_name': VString(min_len=3, max_len=50), 'last_name': VString(min_len=3, max_len=50), 'postcode': VString(), 'state': VString(), 'country': VCountry(required=True, mode='by-name'), 'email': VEmail(required=True), 'password': VPassword(special_chars=3), 'file_upload': VPipe(VUploadFile(mime_types=mime_types, temp_dir='/tmp/formv/test/tmp',), VWatermarkImage(type='image', file=os.path.join(app_root, 'tests/watermarks/copyright.jpg'), opacity=.04, angle=45), VWatermarkImage(text='formv text watermark', angle=25, color=(0,0,0,128), opacity=1), VImprintImage(text='Note the image watermark in the background', color=(0,128,128,255)), VImprintImage(text=datetime.strftime(datetime.utcnow(), 'Uploaded on %Y/%m/%d - %H:%M:%S GMT'), color=(255,128,128,255), margin=(25,10)), ) } chains = { 'coordinates': VCountryPostcode(country_field='country', # extracts (latitude, longitude) pair postcode_field='postcode'), 'password': VEncodedPair(required_field='password', # encodes (password, email) pair required_label='Password', available_field='email'), 'state': VState(country_field='country', # validates state against country state_field='state', mode='by-name'), } class Application(object): def __call__(self, environ, start_response): self.request = Request(make_environ()) response = self.request.get_response(self.response()) return response(self.request.environ, start_response) def response(self): try: form = self.validate(self.request.POST) # - form validation body = to_str(form) except Invalid as e: # - recover successfully uploaded files if isinstance(e.value.get('file_upload'), BaseFileInfo): session.files['file_upload'] = ReusedFileInfo(e.value['file_upload']) body = e.message body += '<br/>errors' body += to_str(e.errors) body += '<br/>values' body += to_str(e.value) response = Response() response.text = unicode(body) if PY2 else body return response def validate(self, request): form = WebForm(allow_missing_keys=True, allow_extra_keys=True, replace_empty_value=True, empty_values={ # inject recovered file back into form if no new file has been uploaded 'file_upload': session.files.get('file_upload'), }) return form.validate(request) application = Application() def make_environ(): ''' simulates a POST request (multipart/form-data) ''' app_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) test_files_dir = os.path.join(app_root, 'tests/files') form = [] # - plain fields form.append(multipart.field('first_name', 'John')) form.append(multipart.field('last_name', 'Smith')) form.append(multipart.field('email', 'dummy@dummy.com')) form.append(multipart.field('postcode', '99501')) form.append(multipart.field('state', 'Alaska')) form.append(multipart.field('country', 'U.S.A.')) form.append(multipart.field('password', 'dummy-secret-password-1')) # - file upload f = os.path.join(test_files_dir, 'test.jpg') form.append(multipart.file('file_upload', filename=f, content_type=guess_type(f, False)[0])) return env(*form) if __name__ == '__main__': from wsgiref.simple_server import make_server httpd = make_server('127.0.0.1', 9000, application) sn = httpd.socket.getsockname() print("Serving HTTP on", sn[0], "port", sn[1], "...") httpd.serve_forever()
Output:
+----------------------------------------------------------------------------------------------------------+ | Field Name | Field Value | +==========================================================================================================+ | coordinates | (61.216799, -149.87828) | +----------------------------------------------------------------------------------------------------------+ | country | 'USA' | +----------------------------------------------------------------------------------------------------------+ | email | 'dummy@dummy.com' | +----------------------------------------------------------------------------------------------------------+ | file_upload | dir_path = '/tmp/formv/test/public' | | | file_date = datetime.datetime(2014, 4, 4, 1, 33, 39, 453000) | | | file_ext ='.jpg' | | | file_name = '16c2493562644b15b4093a02973097b1.jpg' | | | file_object = None | | | file_path = '/tmp/formv/test/public/16c2493562644b15b4093a02973097b1.jpg' | | | file_size = 5129 | | | guid_name ='16c2493562644b15b4093a02973097b1' | | | html_field = 'file_upload' | | | mime_type = 'image/pjpeg' | | | orig_name = 'test.jpg' | | | thumb_name = '16c2493562644b15b4093a02973097b1_th.jpg' | | | thumb_path = '/tmp/formv/test/public/16c2493562644b15b4093a02973097b1_th.jpg' | | | zip_name = None | | | zip_path = None | | | zip_size = None | +----------------------------------------------------------------------------------------------------------+ | first_name | 'John' | +----------------------------------------------------------------------------------------------------------+ | last_name | 'Smith' | +----------------------------------------------------------------------------------------------------------+ | password | '$x-pbkdf2$20$1000$rmQEpiAjI7/FaNpFECFb2w==$l5AfchT7rWwPVxofHhhpSZPu4SJPiU4QTtD/cqmE6og=' | +----------------------------------------------------------------------------------------------------------+ | postcode | '99501' | +----------------------------------------------------------------------------------------------------------+ | state | 'AK' | +----------------------------------------------------------------------------------------------------------+
Geographic configuration:
As of now the configuration available includes only USA. If you have to validates other countries, you have build similar configuration files and place them in the corresponding folders.
Postcodes validation warning:
If you plan to do USA postcodes validation based on the included YAML file, expect a high memory usage as the file contains ~44000 postcodes. A better alternative would be to dump the file in a database, build necessary callables to read the data and instruct formv to use these callables. See formv/configuration.py for an example.
USA postcodes data source:
http://www.boutell.com/zipcodes/ http://www.boutell.com/zipcodes/zipcode.zip
Source:
https://pypi.python.org/pypi/formv
For usage and more examples, see examples & tests.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
formv-0.0.6.tar.gz
(1.7 MB
view details)
File details
Details for the file formv-0.0.6.tar.gz
.
File metadata
- Download URL: formv-0.0.6.tar.gz
- Upload date:
- Size: 1.7 MB
- Tags: Source
- Uploaded using Trusted Publishing? No
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | b7f7a6ddc9e380b09b9e961a7cdbd859f1ba98518036e63f5c2373cae8490fdf |
|
MD5 | 6aeaa0908c46f02a5c477bcfb2f9313a |
|
BLAKE2b-256 | 4f406eacec9a4828263fbda2121d2b236b7ec71a39e464c52f64c493f6b10deb |