{ "info": { "author": "Felipe Ochoa", "author_email": "felipeochoa@find-me-on-github.com", "bugtrack_url": null, "classifiers": [ "Development Status :: 5 - Production/Stable", "Intended Audience :: Developers", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only" ], "description": "fforms: Functional HTML forms for Python\n========================================\n\n|Travis CI build status (Linux)| |Coverage Status|\n\n\n\n``fforms`` is a Python HTML form validation library that allows you to\ntake control of the validation and rendering while minimizing\nboilerplate and awkward constructions. ``fforms`` does not provide any\nwidgets or rendering assistance beyond ease of access to form values\nand validation errors. It does, however, let you define and use custom\nvalidators with extreme ease. Read on to see how!\n\nInstallation\n------------\n\n``fforms`` is a pure Python package with no dependencies, so\ninstalling it should be very straightforward. It's tested on Python\n3.3 through 3.6. [#3.3]_\n\n1. **Option A**: ``pip install fforms``\n2. **Option B**: download the source code, change into the working\n directory, and run ``python setup.py install``\n\nFeatures\n--------\n\n- A familiar interface for use in views\n\n.. code:: python\n\n form = fforms.bind_dotted(registration_form, request.POST, request.FILES)\n if request.method == 'POST':\n if form.is_valid():\n data = form.clean_data\n # do stuff with the data\n return redirect(\"/url/to/redirect\")\n return render_template(\"template-name.html\", form=form)\n\n- Logical data structures that allow easy access from templates\n\n.. code:: html+jinja\n\n {% set field = form.username %}\n \n {% if field.error %}\n {{ field.error }}\n {% endif %}\n\n \n\n- Easy to add custom validators\n\n.. code:: python\n\n myform['product_id'].validator = validators.from_regex(\n \"^[A-Z]{2}-[0-9]{3}-[0-9]{4}$\"\n \"Please provide a valid product ID in the format XX-NNN-NNNN\")\n myform['state'].validator = validators.one_of(\n \"ME\", \"NH\", \"VT\",\n msg=\"We only serve the Northeast\")\n def funky_validator(data):\n left, right = data[:4], data[4:]\n if (int(left, 16), right) not in VALUES:\n raise ValidationError(\"Invalid format\", data)\n # more funky tests\n return process(left, right) # Set the field value\n myform['funky-field'].validator = funky_validator\n\n- Internationalization using your choice of framework.\n\n.. code:: python\n\n def translate_msg(msg, kwargs):\n # msg is a str.format-formatted string, and kwargs has all the\n # field values.\n msg = _(msg)\n kwargs = {key: _(val) for key, val in kwargs.items()}\n return msg.format(**kwargs)\n\n fforms.validators.DeferredMessage.process_message = translate_msg\n\n- Pure Python with no outside dependencies. Don't bring in a gigantic\n web framework just to use their forms library\n\n\nLicense\n-------\n\nThis project is licensed under the MIT license.\n\nGetting started\n---------------\n\nTo use ``fforms``, you'll first need to define a schema for your\nform. This is most easily accomplished by using\n``fforms.schema.make_from_literal`` as follows\n\n.. code:: python\n\n from fforms import make_from_literal, validators, bind_dotted\n schema = make_from_literal({\n 'username': validators.from_regex(\"^[a-zA-Z][a-zA-Z0-9_]{0,25}\"),\n 'password': validators.chain(\n validators.limit_length(min=8, max=128),\n validators.from_regex(\"[a-z]\",\n \"{field.name} must contain lowercase letters\"),\n validators.from_regex(\"[A-Z]\",\n \"{field.name} must contain uppercase letters\"),\n validators.from_regex(\"[0-9]\", \"{field.name} must contain numbers\"),\n validators.from_regex(\"[^a-zA-Z0-9]\",\n \"{field.name} must contain special characters\")\n ),\n 'password2': validators.ensure_str,\n 'email': validators.email,\n 'address': {\n 'street': validators.chain(validators.ensure_str,\n validators.limit_length(min=2)),\n 'street2': validators.ensure_str,\n 'zip_code': validators.chain(validators.from_regex(\"^[0-9]+$\"),\n validators.limit_length(min=5, max=5)),\n 'state': validators.one_of(\"ME\", \"NH\", \"VT\", \"MA\")\n },\n 'tags': [\n {'name': validators.ensure_str}\n ],\n })\n schema.validator = validators.chain(\n schema.validator, # The default is validators.all_children\n validators.key_matcher(\"password\", \"password2\",\n \"Please ensure the two passwords match\"))\n\n schema['tags'].validator = validators.chain(\n schema['tags'].validator,\n validators.limit_length(min=1, max=8)\n )\n\nOnce you have a schema object, you can bind it to data to create a\nbound form object\n\n.. code:: python\n\n form = bind_dotted(schema, {\n 'username': 'felipeochoa',\n 'password': '123abcDEF!@#',\n 'password2': '123ABCdef!@#',\n 'email': 'me@example',\n 'address.street': '123 Main St.',\n 'address.street2': 'Unit 1',\n 'address.zip code': '1234',\n 'tags:0.name': 'tag1',\n 'tags:1.name': 'tag2',\n })\n assert not form.is_valid()\n for field in form:\n print(\"%s %r (%s)\" % (field.name, field.clean_data, field.error))\n\nWhich will print out::\n\n username 'felipeochoa' (None)\n password '123abcDEF!@#' (None)\n password2 '123ABCdef!@#' (None)\n tags [{'name': 'tag1'}, {'name': 'tag2'}] (None)\n address None ()\n email 'me@example' (Enter a valid email address.)\n\n(address does not have an error message of its own; all the errors are\nin its children).\n\nYou can use this code in your views or templates in a conventient\nfashion\n\n.. code:: python\n\n def my_view(request):\n form = bind_dotted(registration_form, request.POST, request.FILES)\n if request.method == 'POST':\n if form.is_valid():\n data = form.clean_data\n # do stuff with the data\n return redirect(\"/url/to/redirect\")\n return render_template(\"template\", form=form)\n\n.. code:: html+jinja\n\n {% if form.error %}\n form.error\n {% endif %}\n {# more stuff #}\n {% for field in form.tags %}\n \n {% endfor %}\n\nDetailed Documentation\n----------------------\n\n``fforms`` operates on three basic concepts: Schema, Validators, and\nForms.\n\n\nSchema\n~~~~~~\n\nThink of a schema like an unbound form. It contains the blueprint for\nbound forms: field names, definitions, and validators. Schema form\ntrees that describe the form you are validating, so some schema can\nhave child schema that perform validation/conversion on a part of the\nreceived data. Schema can be one of the following types\n\n- **MapSchema** Like a Python dictionary, mapping names to\n sub-schema\n- **SequenceSchema** A variable length list where all sub-schema are\n of the same kind.\n- **LeafSchema** Does not contain any children of its own.\n\nAll three types of schema support their own validation, in addition to\nany validation that their children might perform. E.g., if you have a\nschema defined as\n\n.. code:: python\n\n user_schema = {\n 'username': limit_length(max=25),\n 'password': ensure_complexity(numbers=True, uppercase=True),\n 'password2': ensure_str,\n }\n\nYou can add a higher-level validator ``key_matcher('password',\n'password2')`` that additionally verifies that the two values\nmatch. You could then compose that schema into another one, e.g.\n\n.. code:: python\n\n many_users_schema = [\n {\n 'username': limit_length(max=25),\n 'password': ensure_complexity(numbers=True, uppercase=True),\n 'password2': ensure_str,\n }\n ]\n\nThis new schema would accept a list of ``username`` / ``password`` /\n``password2`` combinations, which would be useful if you had to create\nmultiple users at the same time. You could then add a new validator\nto, say, ensure no more than 5 users are created at a time\n``limit_length(max=5)``.\n\nValidators\n~~~~~~~~~~\n\nWe've seen a few validators already, but haven't yet defined what they\nare or what they do. Mechanically, validators are simply functions of\none value that produce another value. The argument is the value\nattached to the field that the validator must check and/or\ntransform. For example, the ``as_int`` validator is defined as follows\n\n.. code:: python\n\n def as_int(data):\n \"Extract an integer from the data.\"\n try:\n return int(data)\n except (TypeError, ValueError):\n raise ValidationError(\"{field.name} must be a whole number\", data)\n\n\nEach schema can have two validators\n\n- **Validator**: The validator stored under ``schema.validator`` will\n be called after the ``form.is_valid`` method is called and will take\n ``field.raw_data`` as its input. The output from this validator will\n be stored as ``field.clean_data``. If the validator raises\n ``ValidationError``, the message from the error will be extracted\n and set as ``field.error``, and ``field.clean_data`` will be left as\n ``None``. For schema with children, validators typically pass\n through the data unmodified, leaving all conversion to leaf\n nodes. In principle though, parent validators could modify the\n values generated by the child validators; **parent validators are\n run after child validators**. Though schema only have one validator,\n the nature of validators means that multiple validators can be easily\n composed into a bigger one.\n- **Pre-processor**: The validator stored under\n ``schema.pre_processor`` will take the raw data provided to the\n field and transform it into a (still serialized) value. The return\n value of the validator will be saved as\n ``field.raw_data``. Pre-processing validators are called as soon as\n the field is bound, so should not do much validation work. In\n particular, the return value should still be \"raw\" (i.e.,\n serialized), since ``field.raw_data`` will be used directly in\n templates. Most applications should have no need to override the\n default pre-processor, which is a no-op. A notable exception is to\n provide a serialized value for any files passed in via\n ``request.FILES`` or similar.\n\nForms/Fields\n~~~~~~~~~~~~\n\nForms (technically ``BoundField`` objects) marry the ``Schema`` with\ndata to be validated. Forms are typically created by calling\n``Schema.bind`` or another helper function like\n``bind_to_dotted``. Forms provide a nice API for accessing errors and\ncleaned data. Like schema, forms are nodes in a tree structure (that\nmirrors the schema they were built for).\n\nOnce you have a form object, calling its ``is_valid`` method will\nreturn ``True`` if validation succeeded for the schema and ``False``\nif it didn't. You will then be able to access the fields' ``.error``\nand ``.clean_data`` arguments. The ``.error`` attribute stores a\nstring with the error message provided by the validator that rejected\nthe data. The ``.clean_data`` stores a de-serialized value that can\nby passed to other parts of your application.\n\n\n\n.. |Travis CI build status (Linux)| image:: https://travis-ci.org/felipeochoa/fforms.svg?branch=master\n :target: https://travis-ci.org/felipeochoa/fforms\n.. |Coverage Status| image:: https://coveralls.io/repos/felipeochoa/fforms/badge.svg\n :target: https://coveralls.io/r/felipeochoa/fforms\n\n.. [#3.3] On Python 3.3 on Windows, the email validator won't work\n with addresses using IPv6 domain names.\n", "description_content_type": null, "docs_url": null, "download_url": "", "downloads": { "last_day": -1, "last_month": -1, "last_week": -1 }, "home_page": "https://github.com/felipeochoa/fforms", "keywords": "forms form html", "license": "MIT", "maintainer": "", "maintainer_email": "", "name": "fforms", "package_url": "https://pypi.org/project/fforms/", "platform": "", "project_url": "https://pypi.org/project/fforms/", "project_urls": { "Homepage": "https://github.com/felipeochoa/fforms" }, "release_url": "https://pypi.org/project/fforms/1.1.1/", "requires_dist": null, "requires_python": "", "summary": "Standalone HTML form validation library", "version": "1.1.1" }, "last_serial": 2619299, "releases": { "1.0": [ { "comment_text": "", "digests": { "md5": "1e067dcf1457982535a71bdba7548ef8", "sha256": "3498dbbae1b715dfd286fa713b57924a530ba2f067692fe4578dd5c34f3a063c" }, "downloads": -1, "filename": "fforms-1.0.zip", "has_sig": false, "md5_digest": "1e067dcf1457982535a71bdba7548ef8", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 24990, "upload_time": "2017-01-20T10:52:34", "url": "https://files.pythonhosted.org/packages/6d/58/72385244f7d3ed8bea870741d2b0f26cefac036564155e576e5dafccfe15/fforms-1.0.zip" } ], "1.1": [ { "comment_text": "", "digests": { "md5": "66bf9429337f7c9792dad4f063e610b6", "sha256": "eac972136ea2919ccd35500a6d0c250dd66a0669285de43ea020ed56ab9e56c7" }, "downloads": -1, "filename": "fforms-1.1.zip", "has_sig": false, "md5_digest": "66bf9429337f7c9792dad4f063e610b6", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 25002, "upload_time": "2017-01-20T10:57:16", "url": "https://files.pythonhosted.org/packages/e4/b6/0afa0ea4a4b57429cb3786b58247118f4d33a054ac7fc56b5f4ff1101266/fforms-1.1.zip" } ], "1.1.1": [ { "comment_text": "", "digests": { "md5": "44195e98a080d0c1cceabfec186ca205", "sha256": "7751bfa2829cc538e6c395b19075938e5d031f67a61dd1d83dbacf1661ab2e9b" }, "downloads": -1, "filename": "fforms-1.1.1.tar.gz", "has_sig": false, "md5_digest": "44195e98a080d0c1cceabfec186ca205", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14917, "upload_time": "2017-02-04T17:55:52", "url": "https://files.pythonhosted.org/packages/29/a6/2d008dde450992a395261d504eee50446e2f18feda306d27f75360ada217/fforms-1.1.1.tar.gz" } ] }, "urls": [ { "comment_text": "", "digests": { "md5": "44195e98a080d0c1cceabfec186ca205", "sha256": "7751bfa2829cc538e6c395b19075938e5d031f67a61dd1d83dbacf1661ab2e9b" }, "downloads": -1, "filename": "fforms-1.1.1.tar.gz", "has_sig": false, "md5_digest": "44195e98a080d0c1cceabfec186ca205", "packagetype": "sdist", "python_version": "source", "requires_python": null, "size": 14917, "upload_time": "2017-02-04T17:55:52", "url": "https://files.pythonhosted.org/packages/29/a6/2d008dde450992a395261d504eee50446e2f18feda306d27f75360ada217/fforms-1.1.1.tar.gz" } ] }