Skip to content

(this is broken package. latest marshmallow is not suppported) a wrapper of marshmallow for form library like behavior

Notifications You must be signed in to change notification settings

podhmo/marshmallow-form

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

54 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

marshmallow-form

motivation

form library is not validation library

What is form library?

  • anyone says, 'it is validation library for post/get data'
  • another says, 'it is rendering library for form element'

marshmallow-form is not above one. form library is 'a container for presentation metadata'. so, form object is just a container.

  • 'rendering form element', it is a task of template library(mako, jinja2, ...).
  • 'validation post/get data', it is a task of schema library(colander, marshmallow, ...).

marshmallow-form is just a metadata container.

features

  • having metadata anywhere
  • nested field support
  • accessing schema
  • building your own form library

getting started

install

from pypi package repository.

# pip install marshmallow-form

from repository

git clone [email protected]/podhmo/marshmallow-form.git
cd marshmallow-form
python setup.py develop

form class definition

the way of form definition.

import marshmallow_form as mf


class PersonForm(mf.Form):
    name = mf.String(label="名前", placeholder="foo", widget="str")
    age = mf.Integer(label="年齢", placeholder="0", widget="int")


class ParentsForm(mf.Form):
    father = mf.Nested(PersonForm, label="父親")
    mother = mf.Nested(PersonForm, label="母親")
  • define form class with marshmallow_form.Form
  • using field classes, define form fields.
  • label and placeholder is metadata for presentation

with template library

with template library, form is just a metadata container. so, a above definition, using form as metadata container.

print(form.father.name["label"]) # => '名前'  # 'name' in japanese
print(form.father.name["placeholder"]) # => 'foo'
print(form.name.value) # => 'foo'
  • accessing metadata with __getitem__.
  • accessing initial data or passed data with .value

with template(mako). deciding rendering method, using widget metadata.

from mako.template import Template

template = Template("""
<%def name="field(f)">\
  ${getattr(self, "field_" + f["widget"])(f)}
</%def>

<%def name="field_str(f)">\
  <input type="text" name="${f.name}" value="${f.value}" placeholder="${f["placeholder"]}"/>
</%def>

<%def name="field_int(f)">\
  <input type="number" name="${f.name}" value="${f.value}" placeholder="${f["placeholder"]}"/>
</%def>

<form action="#" method="POST">
%for f in form:
${field(f)}
%endfor
</form>
""")

print(template.render(form=form))

output.

<form action="#" method="POST">
    <input type="text" name="father.name" value="" placeholder="foo"/>
    <input type="number" name="father.age" value="0" placeholder="0"/>
    <input type="text" name="mother.name" value="" placeholder="foo"/>
    <input type="number" name="mother.age" value="0" placeholder="0"/>
</form>

validation

from marshmallow.validate import Length
from marshmallow import ValidationError


class MLength(Length):
    message_min = 'Too short! {min}.'
    message_max = 'Too long! {max}.'


class AuthenticationForm(mf.Form):
    name = mf.String()
    password = mf.String(validate=MLength(5))
    password_confirm = mf.String()

    @mf.Form.validator
    def same(schema, data):
        if data["password"] != data["password_confirm"]:
            raise ValidationError("not same!", "password")


input_data = {"name": "foo", "password": "*", "password_confirm": "+"}
form = AuthenticationForm(input_data)
print(form.validate())  # False
print(form.errors) # {'password': ['Too short! 5.', 'not same!']}
{'password': ['Too short! 5.', 'not same!']}

detail

having metadata anywhere

  • form metadata
  • field metadata
  • metadata inheritance
  • metadata override

form metadata

import marshmallow_form as mf


class MyForm(mf.Form):
    name = mf.Str()

    class Meta:
        metadata = {"action": "#"}

form = MyForm()
form["action"]  # => #
form.metadata["method"] = "post"
form["method"]  # => "post"

MyForm()["method"]  # => ""

field metadata

class MyForm2(mf.Form):
    name = mf.Str()
    ctime = mf.DateTime(disable=True)


form = MyForm2()
form.ctime["disable"]  # => True

metadata inheritance

from functools import partial
DateTime = partial(mf.DateTime, widget="tdcalendar")


class MyForm3(mf.Form):
    ctime = DateTime()
    utime = DateTime()

form = MyForm3()
form.ctime["widget"]  # => "tdcalendar"
form.utime["widget"]  # => "tdcalendar"

metadata override

class MyForm4(MyForm3):
    class Meta:
        overrides = {"ctime": {"widget": "mycalendar"}}


form = MyForm4()
form.ctime["widget"]  # => "mycalendar"
form.utime["widget"]  # => "tdcalendar"

or with nested

class PersonForm(mf.Form):
    name = mf.String(label="名前", placeholder="foo", widget="str")
    age = mf.Integer(label="年齢", placeholder="0", widget="int")


class ParentsForm(mf.Form):
    father = mf.Nested(PersonForm, label="父親", overrides={"name": {"label": "父親の名前"}})
    mother = mf.Nested(PersonForm, label="母親")

form = ParentsForm()
form.father["label"]  # => "父親"
form.father.name["label"]  # => "父親の名前"
form.mother.name["label"]  # => "名前"

dynamic form

  • modify field
  • add field
  • remove field

modify field

form = StudentForm()
form.color.metadata["pairs"] = [("red", "red"), ("blue", "blue")]
form.color["pairs"]  # => [('red', 'red'), ('blue', 'blue')]

add field

class StudentForm(mf.Form):
    color = mf.Select([])
    name = mf.Str()

form = StudentForm(initial={"grade": 3})
form.add_field("grade", mf.Int(label="学年"))
form.grade.value  # => 3
form.grade["label"]  # => "学年"

[f.name for f in form]  # => ['color', 'name', 'grade']

remove field

form = StudentForm()
form.remove_field("color")

[f.name for f in form]  # => ['name']

accessing schema

  • schema class
  • schema instance

schema class

from collections import namedtuple
Person = namedtuple("Person", "name age")


class PersonForm(mf.Form):
    name = mf.Str()
    age = mf.Int()

    def make_object(self, data):
        return Person(**data)

PersonForm.Schema  # => <class 'marshmallow.schema.PersonSchema'>

schema = PersonForm.Schema(many=True)
schema.dump([Person("foo", 20), Person("bar", 20)]).data
# => OrderedDict([('name', 'foo'), ('age', 20)]), OrderedDict([('name', 'bar'), ('age', 20)])

schema instance

form = PersonForm()
form.schema.load({"name": "foo", "age": 20}).data  # => Person(name='foo', age=20)

building your own form library

  • define your form field class
  • define the way of rendering

define your form field class

if just only adding default metadata, using functools.partial.

import functools
PositiveInt = functools.partial(mf.Int, validate=lambda x: x > 0)

class Form(mf.Form):
    x = PositiveInt()

print(Form({"x": "-10"}).load())
# UnmarshalResult(data=OrderedDict([('x', None)]), errors={'x': ['Validator <lambda>(-10) is False']})

if define your own field class

from marshmallow.fields import Field
from marshmallow.exceptions import UnmarshallingError
import base64


class Base64(Field):
    """ tiny base64 field"""
    def __init__(self, *args, **kwargs):
        super(Base64, self).__init__(*args, **kwargs)

    def _serialize(self, value, attr, obj):
        return base64.encodebytes(value)

    def _deserialize(self, value):
        try:
            return base64.decodebytes(value.encode("utf-8"))
        except:
            raise UnmarshallingError("oops")

MyBase64 = mf.field_factory(Base64)


class Form(mf.Form):
    x = MyBase64(label="this is broken")

form = Form({"x": "MTEx"})
print(form.load())
# UnmarshalResult(data=OrderedDict([('x', b'111')]), errors={})

define the way of rendering

def input(field, placeholder=""):
    fmt = '<input name="{name}" value="{value}" placeholder="{placeholder}">'
    return fmt.format(name=field["name"], value=field.value, placeholder=placeholder)


class Form(mf.Form):
    name = mf.Str(__call__=input)

form = Form()
print(form.name(placeholder="foo"))
# => <input name="" value="" placeholder="foo">

About

(this is broken package. latest marshmallow is not suppported) a wrapper of marshmallow for form library like behavior

Resources

Stars

Watchers

Forks

Packages

No packages published

Languages