zoom package

Submodules

zoom.alerts module

zoom alerts

zoom.apps module

zoom.apps

handles requests by locating and calling a suitable app

class zoom.apps.App

Bases: object

A Zoom application

Looks for a method to satisfy the request. If the method is not specified, it falls back to ‘index’. If there is no method on the app object to satisfy the method then it looks for a file in the app directory by that same name. If it finds such a file it looks for the an entrypoint and if found it calls the entrypoint with the parameters the entrypoint expects.

process(*route, **data)

Process request parameters

static(*args, **kwargs)
class zoom.apps.AppProxy(name, filename, site)

Bases: object

App Proxy

Contains the various extra supporting parts of an app besides the actual functionality. Apps themselves are simply callables that accept a request and return a response. They can be implemented as simple functions or as something more substantial such as a subclass of the App class above. That’s entirely up to the app developer. The AppProxy takes care of the other parts that an app needs, from it’s location to it’s icon to it’s config files.

description

Returns the app description

get_config(default=None)

get the app config

get_icon_view()
keywords

Returns the app keywords

menu()

generate an app menu

method

Returns the app callable entry point

read_config(section, key, default=None)

read app config file values

run(request)

run the app

run_background_jobs()
templates_paths

Calculate Templates Paths

title

Returns the app title

class zoom.apps.NoApp

Bases: object

default app used when no app is available

This is mostly used so the helpers remain valid and return reasonable values when no app is available.

menu = ''
menu_items = []
name = 'none'
theme = None
url = ''
zoom.apps.get_default_app_name(site, user)

get the default app for the currrent user

zoom.apps.get_main_apps(request)

Returns a list of the main apps.

Main apps are apps that appear in the main menu, which is usually found at the top of a web site’s pages. The system menu typically includes apps such as home, admin and the apps app.

This list can be defined in the site.ini file in the [apps] section using the key main. A example is provided in the default site.ini file.

zoom.apps.get_system_apps(request)

Returns a list containing the system apps.

System apps are apps that appear in the system menu, which is usually found at the top left of a web site. The system menu typically includes apps such as login, logout, profile and register.

This list can be defined in the site.ini file in the [apps] section using the key system. A example is provided in the default site.ini file.

zoom.apps.handle(request)

handle a request

zoom.apps.handler(request, next_handler, *rest)

Dispatch request to an application

zoom.apps.helpers(request)

return a dict of app helpers

zoom.apps.load_app(site, name)

get the location of an app by name

zoom.apps.load_module(module, filename)

Dynamically load a module

zoom.apps.main_menu(request)

Returns the main menu.

zoom.apps.main_menu_items(request)

Returns the main menu.

zoom.apps.respond(content, request)

construct a response

zoom.apps.system_menu(request)

Returns the system menu.

zoom.apps.system_menu_items(request)

Returns the system menu.

zoom.audit module

zoom.audit

zoom.auditing module

zoom.auditing

zoom.auditing.audit(action, subject1, subject2='', user=None)

audit an action

zoom.browse module

zoom.browse

zoom.browse.browse(data, **kwargs)

browse data

zoom.buckets module

buckets

stores blobs of data

zoom.buckets.Bucket

alias of zoom.buckets.FileBucket

class zoom.buckets.FileBucket(path, id_factory=<function new_id>)

Bases: object

File Bucket

FileBucket can store and retreive blobs of binary data. This is useful for storing images or files as attachments where you want to put the binary data somewhere and be able to retreive it later if asked. The id returned is generally stored in a database or somewhere else where it is associated with other entities in the system.

FileBucket stores directly into a folder on the drive of the web server where it has sufficient permissions to do so. This is a potential attack area so be careful to take the appropriate precautions.

The location of the buckets is determined by [data] path setting in the site.ini configuration file.

>>> import zoom
>>> site = zoom.sites.Site()
>>> path = os.path.join(site.data_path, 'buckets')
>>> ids = ['id_%04d' % (9-i) for i in range(10)]
>>> new_id = ids.pop
>>> bucket = Bucket(path, new_id)
>>> item_id = bucket.put(b'some data')
>>> item_id
'id_0000'
>>> bucket.get(item_id)
b'some data'
>>> bucket.exists(item_id)
True
>>> bucket.delete(item_id)
>>> bucket.exists(item_id)
False
>>> data = ('%r' % list(range(10))).encode('utf8')
>>> item_id = bucket.put(data)
>>> item_id
'id_0001'
>>> bucket.exists(item_id)
True
>>> bucket.get(item_id)
b'[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]'
>>> bucket.delete(item_id)
>>> bucket = Bucket(path, new_id)
>>> bucket.put(b'some data')
'id_0002'
>>> bucket.put(b'some more data')
'id_0003'
>>> sorted(bucket.keys())
['id_0002', 'id_0003']
>>> for item_id in bucket.keys():
...     bucket.delete(item_id)
delete(item_id)
exists(item_id)
get(item_id, default=None)
keys()
put(item)
zoom.buckets.mkdir_p(path)

make a directory path

Makes a directory path. If it already exists then return without doing anything.

zoom.buckets.new_id()

Generate a random unique id

zoom.cache module

zoom.cache - experimental

Provides a cache mechanism that can be used to add automatic caching to functions and methods. Handy for caching generated pages.

Use as a function or method decorator.

zoom.cache.cached(*keys, **kv)

decorator that caches method results

>>> bunch = zoom.utils.Bunch
>>> key = 'x','abc'
>>> db = zoom.database.setup_test('memory')
>>> zoom.system.request = bunch(app=bunch(name='testapp'))
>>> zoom.system.site = bunch(db=db)
>>> class foo(object):
...     def __init__(self, value):
...         self.value = value
...     @cached(key)
...     def get_value(self, param=''):
...         return self.value + param
>>> clear_cache(key)
>>> a = foo('bar')
>>> a.get_value()
'bar'
>>> a.value = 'bahr'
>>> a.get_value()
'bar'
>>> clear_cache('get_value', key)
>>> a.get_value()
'bahr'
zoom.cache.clear_cache(method_name, *keys)

Clear all entries from cache

zoom.collect module

zoom.collect

class zoom.collect.BasicSearch(collection)

Bases: zoom.collect.RawSearch

Provides basic unindexed field aware search capability

search(text)

Return records that match search text

class zoom.collect.Collection(fields, **kwargs)

Bases: object

A collection of Records

allows(user, action)

Policy rules for shared collection

controller

alias of CollectionController

fields

a fields callable may have data intensive operations, delay execution until it is needed

get_columns()

Return the collection columns.

get_labels()

Return the collection labels.

handle(route, request)

handle a request

has_many_records
locate(key)

locate a record

order(item)

Returns the sort key

process(*args, **data)

Process method parameters

This style of calling collections is useful when you want to make your collection available as an attribute of a Dispatcher.

search(text)

Seach the collection for records matching text

set_fields(fields)

Set the fields to a new value

This can be used for switching fields on the fly, for example when your collection contains entities that are similar enough to be included in the same collection, but that require their own fields.

sorter = None
store = None
store_type

alias of zoom.store.EntityStore

url = None
verbose = True
view

alias of CollectionView

class zoom.collect.CollectionController(collection)

Bases: zoom.mvc.Controller

Perform operations on a Collection

add_image(*_, **kwargs)

accept uploaded images and attach them to the record

after_delete(record)

Things to do after deleting a record

after_insert(record)

Things to do after inserting a record

after_update(record)

Things to do after updating a record

before_delete(record)

Things to do before deleting a record

before_insert(record)

Things to do before inserting a record

before_update(record)

Things to do before updating a record

create_button(*args, **data)

Create a record

delete(key, confirm='yes')

Delete a record

delete_image(key, name)

Delete an image field

reindex(**kwargs)

checks authorization and calls function if authorized

remove_image(*_, **kwargs)

remove a dropzone image

save_button(key, *a, **data)

Save a record

class zoom.collect.CollectionModel

Bases: zoom.models.Model

allows(user, action)

Item level policy

Return a link

url

Return a valid URL

zoom.collect.CollectionRecord

alias of zoom.collect.CollectionModel

class zoom.collect.CollectionStore(store)

Bases: object

Decorate a Store

Provide additional features to a Store class to make it work with collections.

class zoom.collect.CollectionView(collection)

Bases: zoom.mvc.View

View a collection

clear()

Clear the search

delete(key, confirm='yes')

Show a delete form for a collection record

edit(key, **data)

Display an edit form for a record

get_image(*a, **k)

return one of the images from an ImagesField value

image(key, name)

Respond with image field contents

index(q='', *args, **kwargs)

collection landing page

list_images(key=None, value=None)

return list of images for an ImagesField value for this record

new(*args, **kwargs)

Return a New Item form

show(key)

Show a record

class zoom.collect.IndexedCollectionSearch(collection)

Bases: object

Provides token index for fast lookups

We only provide enough room for tokens up to length 20 only because we have to draw the line somewhere. This may result in some records not being found if the search would have mached on characters beyond the position 20.

add(key, values)

Add record values to index

delete(key)

Delete indexed record values

max_token_len = 20
reindex()

Rebuild the collection index

This method indexes a few records at a time, in batches. It can be very slow so should be done only by admins or as part of a maintenance cycle in the background. Once the table is indexed this routine should not be needed. It’s mainly provided to index an already existing table or to replace a damaged index.

search(text)

Return records that match search text

update(key, values)

Update indexed record values

zap()

Delete values for all records

class zoom.collect.RawSearch(collection)

Bases: object

Raw Data Search

add(key, values)

Add record values to index

delete(key)

Delete indexed record values

reindex()
search(text)

Return records that match raw search text

update(key, values)

Update indexed record values

class zoom.collect.SilentCollection(fields, **kwargs)

Bases: zoom.collect.Collection

A collection of Records where we do not audit “View” events

verbose = False
zoom.collect.as_tokens(values, max_len=20)

Return values as a set of tokens

>>> sorted(list(as_tokens(['this is a test', 'other', 'tokentoolongtobecapturedasis'])))
['a', 'is', 'other', 'test', 'this', 'tokentoolongtobecapt']
zoom.collect.image_response(name, data)

provide an image response based on the file extension

zoom.collect.locate(collection, key)

locate a record

zoom.collect.shared_collection_policy(group)

Authourization policy for a shared collection

zoom.component module

zoom.component

Components encapsulate all of the parts that are required to make a component appear on a page. This can include HTML, CSS and Javascript parts and associated libraries.

Components parts are assembled in the way that kind of part needs to be treated. For example HTML parts are simply joined together in order and returned. CSS parts on the other hand are joined together but any duplicate parts are ignored.

When a caller supplies JS or CSS as part of the component being assembled these extra parts are submitted to the system to be included in thier proper place within a response (typically a page template).

The Component object is currently experimental and is intended to be used in future releases.

class zoom.component.Component(*args, **kwargs)

Bases: object

component of a page response

>>> c = Component()
>>> c
<Component: {'html': []}>
>>> c += 'test'
>>> c
<Component: {'html': ['test']}>
>>> c += dict(css='mycss')
>>> c
<Component: {'css': OrderedSet(['mycss']), 'html': ['test']}>
>>> c += dict(css='mycss')
>>> c
<Component: {'css': OrderedSet(['mycss']), 'html': ['test']}>
>>> c += 'test2'
>>> sorted(c.parts.items())
[('css', OrderedSet(['mycss'])), ('html', ['test', 'test2'])]
>>> Component() + 'test1' + 'test2'
<Component: {'html': ['test1', 'test2']}>
>>> Component() + 'test1' + dict(css='mycss')
<Component: {'css': OrderedSet(['mycss']), 'html': ['test1']}>
>>> Component('test1', Component('test2'))
<Component: {'html': ['test1', 'test2']}>
>>> Component(
...    Component('test1', css='css1'),
...    Component('test2', Component('test3', css='css3')),
... )
<Component: {'css': OrderedSet(['css1', 'css3']), 'html': ['test1', 'test2', 'test3']}>
>>> Component((Component('test1', css='css1'), Component('test2', css='css2')))
<Component: {'css': OrderedSet(['css1', 'css2']), 'html': ['test1', 'test2']}>
>>> Component(Component('test1', css='css1'), Component('test2', css='css2'))
<Component: {'css': OrderedSet(['css1', 'css2']), 'html': ['test1', 'test2']}>
>>> zoom.system.parts = Component()
>>> c = Component(Component('test1', css='css1'), Component('test2', css='css2'))
>>> c.render()
'test1test2'
>>> page2 = \
...    Component() + \
...    '<h1>Title</h1>' + \
...    dict(css='mycss') + \
...    dict(js='myjs') + \
...    'page body goes here'
>>> t = (
...    "<Component: {'css': OrderedSet(['mycss']), "
...    "'html': ['<h1>Title</h1>', 'page body goes here'], "
...    "'js': OrderedSet(['myjs'])}>"
... )
>>> #print(repr(page2) + '\n' + t)
>>> repr(page2) == t
True
render()

renders the component

zoom.component.component

alias of zoom.component.Component

zoom.component.compose(*args, **kwargs)

Compose a response - DEPRECATED

zoom.component.handler(request, handler, *rest)

Component handler

zoom.components module

zoom.components

class zoom.components.HeaderBar(model=None, **k)

Bases: zoom.mvc.DynamicView

Header Bar

A horizontal header bar with a left and right content sections. Useful for section headers that may contain actions or other features.

>>> content = HeaderBar(left='Left Part', right='Right Part')
>>> print(content)
<div class="header-container">
    <div class="header-bar clearfix">
        <div class="header-bar-left">
            Left Part
        </div>
        <div class="header-bar-right">
            Right Part
        </div>
    </div>
</div>
left = ''
right = ''

Bases: object

zoom.components.as_actions(items)

returns actions

>>> from zoom.context import context as ctx
>>> ctx.site = lambda: None
>>> ctx.site.url = ''
>>> as_actions(['New'])
'<div class="actions"><ul><li><a class="action" href="<dz:request_path>/new" id="new-action">New</a></li></ul></div>'
>>> as_actions(['New','Delete'])
'<div class="actions"><ul><li><a class="action" href="<dz:request_path>/delete" id="delete-action">Delete</a></li><li><a class="action" href="<dz:request_path>/new" id="new-action">New</a></li></ul></div>'

generate an unordered list of links

>>> as_links(['one', 'two'])
'<ul><li><a href="<dz:app_url>">one</a></li><li><a href="<dz:app_url>/two">two</a></li></ul>'
>>> as_links([('one', '/1'), 'two'])
'<ul><li><a href="/1">one</a></li><li><a href="<dz:app_url>/two">two</a></li></ul>'
>>> as_links([('uno', 'one', '/1'), 'two'], select=lambda a: a.name=='uno')
'<ul><li class="active"><a class="active" href="/1">one</a></li><li><a href="<dz:app_url>/two">two</a></li></ul>'
>>> as_links(['one', 'two'], select=lambda a: a.name=='two')
'<ul><li><a href="<dz:app_url>">one</a></li><li class="active"><a class="active" href="<dz:app_url>/two">two</a></li></ul>'
zoom.components.dropzone(url, **kwargs)

Dropzone component

A basic dropzone component that supports drag and drop uploading of files which are posted to the URL provided.

>>> zoom.system.site = zoom.sites.Site()
>>> zoom.system.site.packages = {}
>>> zoom.system.request = zoom.utils.Bunch(app=zoom.utils.Bunch(name='hello', packages={}))
>>> c = dropzone('/app/files')
>>> isinstance(c, zoom.Component)
True
zoom.components.error(message)
zoom.components.spinner()

A progress spinner

>>> isinstance(spinner(), str)
True
zoom.components.success(message)
zoom.components.warning(message)

zoom.config module

zoom.config

class zoom.config.Config(directory, name, alternate=None)

Bases: object

Config file parser

The Config class looks in two places for config settings. First it looks in the site.ini file corresponding to the current site. If the value being read is not defined there it falls back to the site.ini in the default site. If the value is not found there then it returns the default value provided in the parameter list.

If no value is found it raises and exception.

>>> from zoom.tools import zoompath
>>> config = Config(zoompath('web/sites/default'), 'site.ini')
>>> config.get('site', 'name')
'ZOOM'
>>> config.get('site', 'value_missing', 'Got Default!')
'Got Default!'
>>> missing = False
>>> try:
...     config.get('site', 'value_missing')
... except Exception as e:
...     missing = True
>>> missing
True
>>> config.has_option('site', 'name')
True
>>> config.has_option('section_missing', 'name')
False
>>> missing = False
>>> try:
...     config.get('section_missing', 'name')
... except Exception as e:
...     missing = True
>>> missing
True
>>> target = [
...     ('administrator_group', 'administrators'),
...     ('default', 'guest'),
...     ('developer_group', 'developers')
... ]
>>> sorted(config.items('users')) == target
True
get(section, option, default=None)

Return a configuration value

has_option(section, option)
items(section)

Return a list of (name, value) tuples for each option in a section.

Both the site config and the default sites config files are read and the results combined to produce the list of tuples.

zoom.config.get_config(pathname)

Read a config file into a Config parser

zoom.context module

zoom.context

zoom.cookies module

zoom.cookies

The following test is a bit lame because the timestamp makes it tricky to test. Ideally we’d pass in a timer somehow but the cookie module is handling the time calculation so without diving into the innards of the http.cookies module this is what we have right now.

>>> cookie = SimpleCookie()
>>> add_value(cookie, 'name1', 'value1', 60, True)
>>> v = get_value(cookie)
>>> v.startswith('name1=value1; expires=')
True
>>> v.endswith('; Secure')
True
>>> len(v)
77
zoom.cookies.add_value(cookie, name, value, lifespan, secure)

add a value to a cookie

zoom.cookies.get_cookies(raw_cookie)

extract cookies from raw cookie data

zoom.cookies.get_value(cookie)

get the value portion of a cookie

zoom.cookies.handler(request, handler, *rest)

Cookie handler

>>> request = zoom.utils.Bunch(
...     cookies=None,
...     session_timeout=1,
...     site=zoom.sites.Site(),
... )
>>> response = handler(request, lambda a: zoom.response.Response())
>>> 'zoom_session=' in str(response.cookie)
True
zoom.cookies.new_token()

generate a new subject ID

construct a session cookie

>>> response = zoom.response.HTMLResponse('my page')
>>> set_session_cookie(response, 'sessionid', 'subjectid', 60)
>>> 'zoom_session=sessionid' in str(response.cookie)
True
>>> 'Secure' in str(response.cookie)
True

zoom.database module

zoom.database

a database that does less

class zoom.database.Database(factory, *args, **keywords)

Bases: object

database object

>>> import sqlite3
>>> db = database('sqlite3', database=':memory:')
>>> db('drop table if exists person')
>>> db("""
...     create table if not exists person (
...     id integer primary key autoincrement,
...     name      varchar(100),
...     age       smallint,
...     kids      smallint,
...     birthdate date,
...     salary    decimal(8,2)
...     )
... """)
>>> db("insert into person (name, age) values ('Joe',32)")
1
>>> db('select * from person')
[(1, 'Joe', 32, None, None, None)]
>>> print(db('select * from person'))
id name age kids birthdate salary
-- ---- --- ---- --------- ------
 1 Joe   32 None None      None
>>> create_person_table = """
...     create table if not exists person (
...     id integer primary key autoincrement,
...     name      varchar(100),
...     age       smallint,
...     kids      smallint,
...     birthdate date,
...     salary    decimal(8,2)
...     )
... """
>>> insert_person = "insert into person (name, age) values (%s, %s)"
>>> with database('sqlite3', database=':memory:') as db:
...    db(create_person_table)
...    db(insert_person, 'Joe', 32)
...    db('select * from person')
1
[(1, 'Joe', 32, None, None, None)]
database

Returns an object containing database parameters

debug = False
execute(command, *args)

execute a SQL command with optional parameters

execute_many(command, sequence)

execute a SQL command with a sequence of parameters

classmethod get_stats()

Return the stats to the caller, clearing the list of what is returned

We use a classmethod to support inheritance (over staticmethod)

get_tables()

get a list of database tables

paramstyle = 'pyformat'
report()

produce a SQL log report

stats = []
translate(command, *args)

translate sql dialects

The Python db API standard does not attempt to unify parameter passing styles for SQL arguments. This translate routine attempts to do that for each database type. For databases that use the preferred pyformat paramstyle nothing needs to be done. Databases requiring other paramstyles should override this method to translate the command to the required style.

use(name)

use another database on the same instance

exception zoom.database.DatabaseException

Bases: Exception

exception raised when a database server error occurs

exception zoom.database.EmptyDatabaseException

Bases: Exception

exception raised when a database is empty

class zoom.database.MySQLDatabase(*args, **kwargs)

Bases: zoom.database.Database

MySQL Database

connect_string

Return a string representation of the connection parameters

create_site_tables()

create the tables for a site in a mysql server

create_test_tables()

create the extra test tables

delete_test_tables()

drop the extra test tables

get_column_names(table)

return column names for a table

get_databases()

return database names

get_tables()

return table names

paramstyle = 'pyformat'
transaction()
class zoom.database.MySQLDatabaseTransaction(db)

Bases: zoom.database.Database

class zoom.database.MySQLdbDatabase(*args, **kwargs)

Bases: zoom.database.Database

MySQLdb Database

deprecated - not avaialble for Python 3.6

use MySQLDatabase instead

paramstyle = 'pyformat'
class zoom.database.Result(cursor, array_size=1000)

Bases: object

database query result

first()

return first item in result

class zoom.database.Sqlite3Database(*args, **kwargs)

Bases: zoom.database.Database

Sqlite3 Database

create_site_tables(filename=None)
create_test_tables()

create the extra test tables

delete_test_tables()

drop the extra test tables

get_tables()

return table names

paramstyle = 'qmark'
transaction()
class zoom.database.Sqlite3DatabaseTransaction(db)

Bases: zoom.database.Database

exception zoom.database.UnknownDatabaseException

Bases: Exception

exception raised when the database is unknown

zoom.database.connect_database(config)

establish a database connection

zoom.database.database(engine, *args, **kwargs)

create a database object

zoom.database.handler(request, handler, *rest)

Connect a database to the site if specified

zoom.database.obfuscate(text)

obfuscate text so it is recognizable without divulging it

>>> obfuscate('12345')
'1***5'
>>> obfuscate('')
''
zoom.database.setup_test(engine=None)

create a set of test tables

zoom.exceptions module

zoom.exceptions

System wide exceptions

exception zoom.exceptions.DatabaseException

Bases: Exception

exception zoom.exceptions.DatabaseMissingException

Bases: Exception

Database not found

exception zoom.exceptions.PageMissingException

Bases: Exception

exception zoom.exceptions.SiteMissingException

Bases: Exception

Site directory not found

exception zoom.exceptions.SystemException

Bases: Exception

exception zoom.exceptions.ThemeTemplateMissingException

Bases: Exception

Theme template missing

exception zoom.exceptions.TypeException

Bases: Exception

unsupported type

exception zoom.exceptions.UnauthorizedException

Bases: Exception

exception zoom.exceptions.ValidException

Bases: Exception

invalid record

zoom.fields module

zoom.Fields

class zoom.fields.BasicImageField(label='', *validators, **keywords)

Bases: zoom.fields.Field

Image Field

>>> f = BasicImageField('Photo')
>>> f.initialize(None)
>>> f.value
>>> f.name
'photo'
>>> i = BasicImageField('Photo')
>>> i.initialize({'photo': b'data blob', 't':12})
>>> i.value
b'data blob'
>>> i.display_value()
'<img alt="Photo" class="image-field-image" src="image?name=photo" />'
alt = None
assign(value)

Assign a value to a BasicImageField

css_class = 'image-field'
default = None
display_value()

Display BasicImageField value

BasicImageFields rely on the controller of the current resource providing an image method so that when it wants to display the associted image, it uses a standard img field which calls back to the app to get the image.

evaluate()

Evaluate a BasicImageField

no_image_url = '/static/zoom/images/no_image.png'
requires_multipart_form()

Returns True because images require multipart forms

update(**values)

Update the ImageField field values

We override the superclass here because we need behaviour specific to how image fields arrive from forms.

updated_value = None
url = None
value = None
widget()

Returns the BasicImageField widget

BasicImageField use an HTML input of type “file” to collect the filename of the image to be uploaded.

If the field aready has a value, the BasicImageField also provides a view of the current image with a link for deleting the current image. BasicImageField relies on the current controller to provide the delete-image method.

class zoom.fields.BirthdateField(label='', *validators, **keywords)

Bases: zoom.fields.DateField

css_class = 'birthdate_field'
maxlength = 12
size = 12
class zoom.fields.Button(caption='Save', **keywords)

Bases: zoom.fields.Field

Button field.

>>> Button('Save').show()
''
>>> Button('Save').edit()
'<input class="button" type="submit" id="save_button" name="save_button" value="Save" />'
>>> Button('Save', cancel='/app/cancel').edit()
'<input class="button" type="submit" id="save_button" name="save_button" value="Save" />&nbsp;<a href="/app/cancel">cancel</a>'
as_searchable()

Return searchable parts of field

>>> name_field = Field('Name', default='default test')
>>> name_field.as_searchable()
{'default test'}
>>> name_field = Field('Name', value='test')
>>> name_field.as_searchable()
{'test'}
>>> name_field = Field('Age', value=10)
>>> name_field.as_searchable()
{'10'}
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.as_searchable()
{'こんにちは'}
>>> name_field.visible = False
>>> name_field.as_searchable()
set()
>>> EmailField('Email', value='test@testco.com').as_searchable()
{'test@testco.com'}
edit()

edit the field

evaluate()

Evaluate field value.

Return the value of the field expressed as key value pair (dict) ususally to be combined with other fields in the native type where the value is the native data type for the field type.

show()

show the field

class zoom.fields.ButtonField(caption='Save', **keywords)

Bases: zoom.fields.Button

Button field.

>>> ButtonField('Save').show()
''
>>> print(ButtonField('Save').edit())
<div class="field">
  <div class="field_label">&nbsp;</div>
  <div class="field_edit"><input class="button" type="submit" id="save_button" name="save_button" value="Save" /></div>
</div>
edit()

edit the field

evaluate()

Evaluate field value.

Return the value of the field expressed as key value pair (dict) ususally to be combined with other fields in the native type where the value is the native data type for the field type.

class zoom.fields.Buttons(captions=['Save'], **keywords)

Bases: zoom.fields.Field

>>> Buttons(['Save','Publish','Delete']).show()
''
>>> Buttons(['Save','Publish']).widget()
'<input class="button" type="submit" id="save_button" name="save_button" value="Save" />&nbsp;<input class="button" type="submit" id="publish_button" name="publish_button" value="Publish" />'
>>> Buttons(['Save'], cancel='/app/id').widget()
'<input class="button" type="submit" id="save_button" name="save_button" value="Save" />&nbsp;<a href="/app/id">cancel</a>'
as_searchable()

Return searchable parts of field

>>> name_field = Field('Name', default='default test')
>>> name_field.as_searchable()
{'default test'}
>>> name_field = Field('Name', value='test')
>>> name_field.as_searchable()
{'test'}
>>> name_field = Field('Age', value=10)
>>> name_field.as_searchable()
{'10'}
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.as_searchable()
{'こんにちは'}
>>> name_field.visible = False
>>> name_field.as_searchable()
set()
>>> EmailField('Email', value='test@testco.com').as_searchable()
{'test@testco.com'}
edit()

edit the field

evaluate()

Evaluate field value.

Return the value of the field expressed as key value pair (dict) ususally to be combined with other fields in the native type where the value is the native data type for the field type.

show()

show the field

widget()

returns the field widget

class zoom.fields.ButtonsField(captions=['Save'], **keywords)

Bases: zoom.fields.Buttons

Buttons field.

>>> ButtonsField('Save').show()
''
>>> print(ButtonsField(['Save','Publish']).edit())
<div class="field">
  <div class="field_label">&nbsp;</div>
  <div class="field_edit"><input class="button" type="submit" id="save_button" name="save_button" value="Save" />&nbsp;<input class="button" type="submit" id="publish_button" name="publish_button" value="Publish" /></div>
</div>
edit()

edit the field

class zoom.fields.CheckboxField(label='', *validators, **keywords)

Bases: zoom.fields.TextField

Checkbox Field

>>> CheckboxField('Done').display_value()
'no'
>>> CheckboxField('Done', value=True).display_value()
'yes'
>>> CheckboxField('Done').widget()
'<input class="checkbox_field" type="checkbox" id="done" name="done" />'
>>> f = CheckboxField('Done', value=True)
>>> f.widget()
'<input checked class="checkbox_field" type="checkbox" id="done" name="done" />'
>>> f.validate(**{'DONE': 'on'})
True
>>> f.evaluate()
{'done': True}
>>> f = CheckboxField('Done')
>>> f.widget()
'<input class="checkbox_field" type="checkbox" id="done" name="done" />'
>>> f.evaluate()
{'done': None}
>>> f.validate(**{})
True
>>> f.evaluate()
{'done': None}
>>> f = CheckboxField('Done')
>>> f.widget()
'<input class="checkbox_field" type="checkbox" id="done" name="done" />'
>>> f.evaluate()
{'done': None}
>>> f.validate(**{'DONE': 'on'})
True
>>> f.evaluate()
{'done': True}
>>> f = CheckboxField('Done', options=['yes','no'], value=False)
>>> f
<Field name='done' value=False>
>>> f.validate(**{'done': True})
True
>>> f
<Field name='done' value=True>
>>> f.validate(**{'DoNE': False})
True
>>> f
<Field name='done' value=False>
>>> f.validate(**{'done': 'on'})
True
>>> f
<Field name='done' value='on'>
>>> f.display_value()
'yes'
>>> f.evaluate()
{'done': True}
>>> f = CheckboxField('Done', options=['yep','nope'], default=True)
>>> f.evaluate()
{'done': True}
>>> f.widget()
'<input checked class="checkbox_field" type="checkbox" id="done" name="done" />'
>>> f.update(other='test')
>>> f.widget()
'<input class="checkbox_field" type="checkbox" id="done" name="done" />'
>>> f = CheckboxField('Done', options=['yep','nope'])
>>> f.evaluate()
{'done': None}
>>> f.validate(**{'OTHERDATA': 'some value'})
True
>>> f.evaluate()
{'done': False}
>>> CheckboxField('Done', options=['yep','nope']).display_value()
'nope'
>>> CheckboxField('Done', options=['yep','nope'], default=False).display_value()
'nope'
>>> CheckboxField('Done', options=['yep','nope'], default=True).display_value()
'nope'
>>> CheckboxField('Done', options=['yep','nope'], default=True).evaluate()
{'done': True}
>>> CheckboxField('Done', options=['yep','nope'], default=True, value=False).display_value()
'nope'
>>> CheckboxField('Done', options=['yep','nope'], value=True).display_value()
'yep'
>>> CheckboxField('Done', options=['yep','nope'], value=False).evaluate()
{'done': False}
>>> CheckboxField('Done', options=['yep','nope'], value='True').value
'True'
assign(value)

assign a value to the field

default = None
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
evaluate()

Evaluate field value.

Return the value of the field expressed as key value pair (dict) ususally to be combined with other fields in the native type where the value is the native data type for the field type.

options = ['yes', 'no']
show()

show the field

truthy = [True, 'True', 'yes', 'on']
update(**values)

Update field.

>>> name_field = Field('Name', value='Sam')
>>> name_field.value
'Sam'
>>> name_field.update(city='Vancouver')
>>> name_field.value
'Sam'
>>> name_field.update(name='Joe')
>>> name_field.value
'Joe'
>>> name_field.update(NaMe='Adam')
>>> name_field.value
'Adam'
value = None
widget()

returns the field widget

class zoom.fields.CheckboxesField(label='', *validators, **keywords)

Bases: zoom.fields.Field

Checkboxes field.

>>> cb = CheckboxesField('Select', value='One', values=['One','Two','Three'], hint='test hint')
>>> print(cb.widget())
<ul class="checkbox_field">
<li><input checked class="checkbox_field" type="checkbox" id="select" name="select" value="One" /><div>One</div></li>
<li><input class="checkbox_field" type="checkbox" id="select" name="select" value="Two" /><div>Two</div></li>
<li><input class="checkbox_field" type="checkbox" id="select" name="select" value="Three" /><div>Three</div></li>
</ul>
show()

show the field

widget()

returns the field widget

class zoom.fields.ChosenMultiselectField(*a, **k)

Bases: zoom.fields.MultiselectField

Chosen Multiselect field.

>>> from zoom.component import Component
>>> f = ChosenMultiselectField('Choose', options=['One','Two','Three'], hint='test hint')
>>> print(f.widget())
<select data-placeholder="Select Choose" multiple="multiple" class="chosen" name="choose" id="choose">
<option value="One">One</option>
<option value="Two">Two</option>
<option value="Three">Three</option>
</select>
>>> f = ChosenMultiselectField('Choose', options=['One','Two','Three'], hint='test hint', placeholder='my placeholder')
>>> print(f.widget())
<select data-placeholder="my placeholder" multiple="multiple" class="chosen" name="choose" id="choose">
<option value="One">One</option>
<option value="Two">Two</option>
<option value="Three">Three</option>
</select>
>>> f = ChosenMultiselectField('Choose', value='2', options=['One', 'Two', 'Three'])
>>> print(f.widget())
<select data-placeholder="Select Choose" multiple="multiple" class="chosen" name="choose" id="choose">
<option value="One">One</option>
<option value="Two">Two</option>
<option value="Three">Three</option>
</select>
>>> f = ChosenMultiselectField('Choose', value='Two', options=['One', 'Two', 'Three'])
>>> print(f.widget())
<select data-placeholder="Select Choose" multiple="multiple" class="chosen" name="choose" id="choose">
<option value="One">One</option>
<option value="Two" selected>Two</option>
<option value="Three">Three</option>
</select>
>>> f = ChosenMultiselectField('Choose', value='2', options=[('One', '1'), ('Two', '2'), ('Three', '3')])
>>> print(f.widget())
<select data-placeholder="Select Choose" multiple="multiple" class="chosen" name="choose" id="choose">
<option value="1">One</option>
<option value="2" selected>Two</option>
<option value="3">Three</option>
</select>
>>> f = ChosenMultiselectField('Choose', value='Two', options=[('One', '1'), ('Two', '2'), ('Three', '3')])
>>> print(f.widget())
<select data-placeholder="Select Choose" multiple="multiple" class="chosen" name="choose" id="choose">
<option value="1">One</option>
<option value="2" selected>Two</option>
<option value="3">Three</option>
</select>
>>> f = ChosenMultiselectField('Choose', value=['Two', 3], options=[('One', '1'), ('Two', '2'), ('Three', '3')])
>>> print(f.widget())
<select data-placeholder="Select Choose" multiple="multiple" class="chosen" name="choose" id="choose">
<option value="1">One</option>
<option value="2" selected>Two</option>
<option value="3" selected>Three</option>
</select>
>>> f = ChosenMultiselectField('Choose', value=['One', 3], options=[('One', 1), ('Two', 2), ('Three', 3)])
>>> print(f.widget())
<select data-placeholder="Select Choose" multiple="multiple" class="chosen" name="choose" id="choose">
<option value="1" selected>One</option>
<option value="2">Two</option>
<option value="3" selected>Three</option>
</select>
css_class = 'chosen'
select_layout = '<select data-placeholder="{}" multiple="multiple" class="{}" name="{}" id="{}">\n'
widget()

returns the field widget

class zoom.fields.ChosenSelectField(*a, **k)

Bases: zoom.fields.PulldownField

css_class = 'chosen'
libs = ['/static/zoom/chosen/chosen.jquery.js']
select_layout = '<select data-placeholder="{place}" class="{classed}" name="{name}" id="{name}">\n'
styles = ['/static/zoom/chosen/chosen.css']
class zoom.fields.DataURIAttachmentsField(label='', *validators, **keywords)

Bases: zoom.fields.Field

An Attachments field - DEPRECATED

this field stores the data within the database this field uses dropzone.js heavily the results are shown via a Data URI

this field stores the data within the database this field uses dropzone.js heavily the results are shown via a Data URI multiple dropzones supported by assuming you will bind ONLY one to the form

and the others to an element via the “selector” configuration option

TODO: with multiple dropzones, support submit when the master/form is empty This field makes some assumptions about what you want todo:

  • this field uses dropzone.js
  • the field expects you want todo a native form submission (once vs. multiple ajax calls)
  • this field stores the data within the database
  • the results are shown via a Data URI which is not always optimal
  • it looks like dropzone.js only adds the form fields when dropzone is bound
    to a form (i.e. binding to dropzone within a form skips this - assumes xhr)
  • due to assumption to mimic native form, the data makes round trips to/from server
>>> icon = DataURIAttachmentsField('Icon')
>>> icon.requires_multipart_form()
True
>>> icon.assign(None)
>>> assert icon.value == icon.default
>>> class PsuedoFile(object):
...     @property
...     def name(self):
...         return 'field_name'
...     @property
...     def filename(self):
...         return 'filename.png'
...     @property
...     def value(self):
...         return b''
>>> icon.assign([PsuedoFile(), PsuedoFile()])
>>> assert isinstance(icon.value, list)
>>> icon.value
[['field_name', ['filename.png', '']], ['field_name', ['filename.png', '']]]
as_searchable()

Return searchable parts of field

>>> name_field = Field('Name', default='default test')
>>> name_field.as_searchable()
{'default test'}
>>> name_field = Field('Name', value='test')
>>> name_field.as_searchable()
{'test'}
>>> name_field = Field('Age', value=10)
>>> name_field.as_searchable()
{'10'}
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.as_searchable()
{'こんにちは'}
>>> name_field.visible = False
>>> name_field.as_searchable()
set()
>>> EmailField('Email', value='test@testco.com').as_searchable()
{'test@testco.com'}
assign(value={})

assign a value to the field

capitalize

return the field id capitalized

classed = 'dropzone nojs'
configuration

configure the Dropzone .js assets

this field is designed to work within an existing/native form. As such, we turn off the auto processing of the queue (AJAX push) to bulk send the form all at once.

css = '<link href="/static/zoom/dropzone/dropzone.min.css" rel="stylesheet" type="text/css" />'
datauri(image)

return the data URI string

default = []
display_value()

web based display view of the field

edit()

edit the field

maximum_files = 5
mockFile

return the saved file - this is a .js call and injected into the .js

no_image_url = 'https://placehold.it/350x150'
requires_multipart_form()

return True if a multipart form is required for this field

script = ['<script type="text/javascript" src="/static/zoom/dropzone/dropzone.min.js"></script>', '<script type="text/javascript">Dropzone.autoDiscover = false;</script>']
selector = '#zoom_form'
update(**values)

update the field

widget()

return the dropzone widget

class zoom.fields.DataURIImageField(label='', *validators, **keywords)

Bases: zoom.fields.DataURIAttachmentsField

An Attachments field making use of a Data URI where we limit to a single file

maximum_files = 1
zoom.fields.DataURIImagesField

alias of zoom.fields.DataURIAttachmentsField

class zoom.fields.DateField(label='', *validators, **keywords)

Bases: zoom.fields.Field

Date Field

DatField values can be either actual dates (datetime.date) or string representations of dates. Values coming from databases or from code will typically be dates, while dates coming in from forms will typically be strings.

DateFields always evaluate to date types and always display as string representations of those dates formatted according to the specified format.

>>> DateField("Start Date").widget()
'<input class="date_field" type="text" id="start_date" maxlength="12" name="start_date" value="" />'
>>> from datetime import date, datetime
>>> f = DateField("Start Date")
>>> f.display_value()
''
>>> f.assign('')
>>> f.display_value()
''
>>> f = DateField("Start Date", value=date(2015,1,1))
>>> f.value
datetime.date(2015, 1, 1)
>>> f = DateField("Start Date", value=datetime(2015,1,1))
>>> f.value
datetime.datetime(2015, 1, 1, 0, 0)
>>> f.evaluate()
{'start_date': datetime.date(2015, 1, 1)}
>>> f.assign('Jan 01, 2015') # forms assign with strings
>>> f.display_value()
'Jan 01, 2015'
>>> f.evaluate()
{'start_date': datetime.date(2015, 1, 1)}
>>> f.assign('2015-12-31') # forms assign with strings
>>> f.display_value()
'Dec 31, 2015'
>>> f.evaluate()
{'start_date': datetime.date(2015, 12, 31)}
>>> f.assign(date(2015,1,31))
>>> f.display_value()
'Jan 31, 2015'
>>> f.assign('TTT 01, 2015')
>>> f.display_value()
'TTT 01, 2015'
>>> failed = False
>>> try:
...     f.evaluate()
... except ValueError:
...     failed = True
>>> failed
True
>>> DateField("Start Date", value=date(2015,1,1)).widget()
'<input class="date_field" type="text" id="start_date" maxlength="12" name="start_date" value="Jan 01, 2015" />'
alt_input_format = '%Y-%m-%d'
as_searchable()

Return searchable parts of field

>>> from datetime import date, datetime
>>> f = DateField("Start Date")
>>> f.assign(date(2015,1,31))
>>> f.display_value()
'Jan 31, 2015'
>>> f.as_searchable()
{'2015-01-31 01-31-2015 Saturday January 31 2015'}
css_class = 'date_field'
default = None
display_value(alt_format=None)

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
evaluate()

Evaluate field value.

Return the value of the field expressed as key value pair (dict) ususally to be combined with other fields in the native type where the value is the native data type for the field type.

format = '%b %d, %Y'
input_format = '%b %d, %Y'
max = None
maxlength = 12
min = None
search_fmt = '{:%Y-%m-%d %m-%d-%Y %A %B %-d %Y}'
show()

show the field

size = 12
validators = [<zoom.validators.DateValidator object>]
value = None
widget()

returns the field widget

class zoom.fields.DecimalField(label='', *validators, **keywords)

Bases: zoom.fields.NumberField

Decimal Field

>>> DecimalField('Count',value="2.1").display_value()
'2.1'
>>> DecimalField('Count', value=Decimal('10.24')).widget()
'<input class="decimal_field" type="text" id="count" maxlength="10" name="count" size="10" value="10.24" />'
>>> DecimalField('Count').widget()
'<input class="decimal_field" type="text" id="count" maxlength="10" name="count" size="10" value="" />'
>>> n = DecimalField('Size')
>>> n.assign('2.1')
>>> n.value
Decimal('2.1')
>>> n.assign(0)
>>> n.value
Decimal('0')
>>> n.assign('0')
>>> n.value
Decimal('0')
>>> n.assign('2.1')
>>> n.value
Decimal('2.1')
>>> n.assign('')
>>> n.evaluate()
{'size': None}
>>> DecimalField('Hours').evaluate()
{'hours': 0}
converter

alias of decimal.Decimal

css_class = 'decimal_field'
maxlength = 10
size = 10
value = 0
class zoom.fields.EditField(label='', *validators, **keywords)

Bases: zoom.fields.MemoField

Large textedit.

>>> EditField('Notes').widget()
'<textarea class="edit_field" height="6" id="notes" name="notes" size="10"></textarea>'
css_class = 'edit_field'
edit()

edit the field

height = 6
size = 10
value = ''
widget()

returns the field widget

class zoom.fields.EmailField(label, *validators, **keywords)

Bases: zoom.fields.TextField

Email field

>>> EmailField('Email').widget()
'<input class="text_field" id="email" maxlength="60" name="email" size="30" type="text" value="" />'
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
maxlength = 60
size = 30
class zoom.fields.Field(label='', *validators, **keywords)

Bases: object

Field base class

as_dict()
as_searchable()

Return searchable parts of field

>>> name_field = Field('Name', default='default test')
>>> name_field.as_searchable()
{'default test'}
>>> name_field = Field('Name', value='test')
>>> name_field.as_searchable()
{'test'}
>>> name_field = Field('Age', value=10)
>>> name_field.as_searchable()
{'10'}
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.as_searchable()
{'こんにちは'}
>>> name_field.visible = False
>>> name_field.as_searchable()
set()
>>> EmailField('Email', value='test@testco.com').as_searchable()
{'test@testco.com'}
assign(value)

assign a value to the field

browse = True
clean(*args, **kwargs)

Update (sometimes ammended values) and validate a field.

>>> from zoom.validators import Cleaner, required
>>> upper = Cleaner(str.upper)
>>> name_field = Field('Name', upper, required)
>>> name_field.clean(city='Vancouver')
False
>>> name_field.validate(name='Vancouver')
True
>>> name_field.value
'Vancouver'
>>> name_field.clean(name='Vancouver')
True
>>> name_field.value
'VANCOUVER'
default = ''
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
edit()

edit the field

evaluate()

Evaluate field value.

Return the value of the field expressed as key value pair (dict) ususally to be combined with other fields in the native type where the value is the native data type for the field type.

field_layout = <zoom.fields.FieldLayout object>
hint = ''
initialize(*a, **k)

Initialize field value.

Set field value according to value passed in as parameter or if there is not value for this field, set it to the default value for the field.

>>> f = Field('test', default='zero')
>>> f.initialize(test='one')
>>> f.value
'one'
>>> r = dict(test='two')
>>> f.initialize(r)
>>> f.value
'two'
>>> r = dict(not_test='two')
>>> f.initialize(r)
>>> f.value
'zero'
label = ''
layout(label, content, edit=True)
msg = ''
options = []
placeholder = None
render_hint()

Render hint.

>>> name_field = Field('Name', hint='Full name')
>>> name_field.render_hint()
'<span class="hint">Full name</span>'
render_msg()

Render validation error message.

>>> from zoom.validators import required
>>> name_field = Field('Name', required)
>>> name_field.update(NAME='')
>>> name_field.valid()
False
>>> name_field.render_msg()
'<span class="wrong">required</span>'
requires_multipart_form()

return True if a multipart form is required for this field

show()

show the field

update(**values)

Update field.

>>> name_field = Field('Name', value='Sam')
>>> name_field.value
'Sam'
>>> name_field.update(city='Vancouver')
>>> name_field.value
'Sam'
>>> name_field.update(name='Joe')
>>> name_field.value
'Joe'
>>> name_field.update(NaMe='Adam')
>>> name_field.value
'Adam'
valid()

Validate field value.

>>> from zoom.validators import required
>>> name_field = Field('Name',required)
>>> name_field.update(NAME='Fred')
>>> name_field.valid()
True
>>> name_field.update(NAME='')
>>> name_field.valid()
False
>>> name_field.msg
'required'
validate(*a, **k)

Update and validate a field.

>>> from zoom.validators import required
>>> name_field = Field('Name',required)
>>> name_field.validate(city='Vancouver')
False
>>> name_field.validate(name='Fred')
True
>>> name_field.value
'Fred'
validators = []
value = ''
visible = True
widget()

returns the field widget

wrap = ' nowrap'
class zoom.fields.FieldIterator(fields)

Bases: object

class zoom.fields.FieldLayout

Bases: object

edit(field)
field_template = '<div class="field">\n <div class="field_label">{label}</div>\n <div class="field_{mode}">{content}</div>\n</div>\n'
hint_template = '<table class="transparent">\n <tr>\n <td{wrap}>{widget}</td>\n <td>\n <div class="hint">{hints}</div>\n </td>\n </tr>\n</table>\n'
class zoom.fields.Fields(*args)

Bases: object

A collection of field objects.

>>> fields = Fields(TextField('Name'), PhoneField('Phone'))
>>> print(fields.edit())
<div class="field">
  <div class="field_label">Name</div>
  <div class="field_edit"><table class="transparent">
    <tr>
        <td nowrap><input class="text_field" id="name" maxlength="40" name="name" size="40" type="text" value="" /></td>
        <td>
            <div class="hint"></div>
        </td>
    </tr>
</table>
</div>
</div>
<div class="field">
  <div class="field_label">Phone</div>
  <div class="field_edit"><table class="transparent">
    <tr>
        <td nowrap><input class="text_field" id="phone" name="phone" size="20" type="text" value="" /></td>
        <td>
            <div class="hint"></div>
        </td>
    </tr>
</table>
</div>
</div>
>>> from zoom.utils import pp
>>> fields = Fields(TextField('Name', value='Amy'), PhoneField('Phone', value='2234567890'))
>>> pp(fields.as_dict())
{
  'name' ...........: <Field name='name' value='Amy'>
  'phone' ..........: <Field name='phone' value='2234567890'>
}
>>> fields = Fields(TextField('Name'), MemoField('Notes'))
>>> fields.validate({'name': 'Test'})
True
>>> d = fields.evaluate()
>>> d['name']
'Test'
>>> len(d['notes'])
0
>>> fields.validate({'notes': 'here are some notes'})
True
>>> d = fields.evaluate()
>>> len(d['notes'])
19
>>> pp(fields.as_dict())
{
  'name' ...........: <Field name='name' value='Test'>
  'notes' ..........: <Field name='notes' value='here are some notes'>
}
>>> record = dict(name='Adam', notes='no text here')
>>> pp(record)
{
  "name": "Adam",
  "notes": "no text here"
}
>>> record.update(fields)
>>> record['name']
'Test'
>>> len(record['notes'])
19
as_dict()
as_list()
>>> fields = Fields(TextField('Name', value='Amy'), PhoneField('Phone', value='2234567890'))
>>> fields.as_list()
[<Field name='name' value='Amy'>, <Field name='phone' value='2234567890'>]
as_searchable()

Return fields as a set of searchable items

>>> from zoom.utils import pp
>>> fields = Fields(
...     TextField('Name', value='Amy'),
...     PhoneField('Phone', value='2234567890'),
...     DateField('Birthdate', value=datetime.date(1980,1,1)),
...     MultiselectField(
...         'Type',
...         value=['One','dos'],
...         options=[('One','uno'),('Two','dos')]
...     )
... )
>>> pp(sorted(map(str, fields.as_searchable())))
[
  "1980-01-01 01-01-1980 Tuesday January 1 1980",
  "2234567890",
  "Amy",
  "One",
  "Two"
]
clean(*args, **kwargs)
display_value()
>>> from zoom.utils import pp
>>> fields = Fields(TextField('Name', value='Amy'), PhoneField('Phone', value='2234567890'))
>>> pp(fields.display_value())
{
  "name": "Amy",
  "phone": "2234567890"
}
edit()
evaluate()
>>> from zoom.utils import pp
>>> fields = Fields(TextField('Name', value='Amy'), PhoneField('Phone', value='2234567890'))
>>> pp(fields.evaluate())
{
  "name": "Amy",
  "phone": "2234567890"
}
initialize(*a, **k)

Initialize Field values

>>> from zoom.utils import pp
>>> fields = Fields(TextField('Name', value='Amy'), PhoneField('Phone', value='2234567890'))
>>> fields.initialize(phone='987654321')
>>> pp(fields.as_dict())
{
  'name' ...........: <Field name='name' value=''>
  'phone' ..........: <Field name='phone' value='987654321'>
}
requires_multipart_form()
show()
update(*a, **k)

Update Field values

>>> from zoom.utils import pp
>>> fields = Fields(TextField('Name', value='Amy'), PhoneField('Phone', value='2234567890'))
>>> fields.update(phone='987654321')
>>> pp(fields.as_dict())
{
  'name' ...........: <Field name='name' value='Amy'>
  'phone' ..........: <Field name='phone' value='987654321'>
}
valid()
validate(*a, **k)
class zoom.fields.Fieldset(label, fields, hint='')

Bases: zoom.fields.Fields

A collection of field objects with an associated label.

>>> print(Section('Personal',[TextField('Name',value='Joe')]).show())
<h2>Personal</h2>
<div class="field">
  <div class="field_label">Name</div>
  <div class="field_show">Joe</div>
</div>
edit()
render_hint()
show()
class zoom.fields.FileField(label='', *validators, **keywords)

Bases: zoom.fields.TextField

>>> FileField('Document').widget()
'<input class="file_field" id="document" name="document" type="file" value="None" />'
assign(value)

assign a value to the field

css_class = 'file_field'
default = 'None'
requires_multipart_form()

return True if a multipart form is required for this field

value = 'None'
class zoom.fields.FloatField(label='', *validators, **keywords)

Bases: zoom.fields.NumberField

Float Field

>>> FloatField('Count', value=2.1).display_value()
'2.1'
>>> FloatField('Count').widget()
'<input class="float_field" type="text" id="count" maxlength="10" name="count" size="10" value="" />'
>>> n = FloatField('Size')
>>> n.assign(2.1)
>>> n.value
2.1
>>> n.assign(0)
>>> n.value
0.0
>>> n.assign('0')
>>> n.value
0.0
>>> n.assign('2.1')
>>> n.value
2.1
>>> n.assign('')
>>> n.evaluate()
{'size': None}
converter

alias of builtins.float

css_class = 'float_field'
evaluate()

Evaluate field value.

Return the value of the field expressed as key value pair (dict) ususally to be combined with other fields in the native type where the value is the native data type for the field type.

maxlength = 10
size = 10
value = 0
class zoom.fields.Hidden(label='', *validators, **keywords)

Bases: zoom.fields.Field

Hidden field.

>>> Hidden('Hide Me').show()
''
>>> Hidden('Hide Me', value='test').edit()
'<input type="hidden" id="hide_me" name="hide_me" value="test" />'
edit()

edit the field

visible = False
zoom.fields.ImageField

alias of zoom.fields.BasicImageField

class zoom.fields.ImagesField(label='', *validators, **keywords)

Bases: zoom.fields.Field

Display a drag and drop multiple image storage field

this field is experimental - may change for a while yet

>>> ImagesField('Photo')
<Field name='photo' value=None>
default = '0f3c648630874d50adb45308a5300021'
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
show()

show the images field

url = ''
value = None
widget()

returns the field widget

wrap = ''
class zoom.fields.IntegerField(label='', *validators, **keywords)

Bases: zoom.fields.TextField

Integer Field

>>> IntegerField('Count', value=2).display_value()
'2'
>>> IntegerField('Count').widget()
'<input class="number_field" id="count" maxlength="10" name="count" size="10" type="text" value="" />'
>>> n = IntegerField('Size')
>>> n.assign('2')
>>> n.value
2
>>> n.evaluate()
{'size': 2}
>>> n = IntegerField('Size', units='meters')
>>> n.assign('22234')
>>> n.value
22234
>>> n.display_value()
'22,234 meters'
>>> n.assign('')
>>> n.evaluate()
{'size': ''}
as_searchable()

Return searchable parts of field

>>> name_field = Field('Name', default='default test')
>>> name_field.as_searchable()
{'default test'}
>>> name_field = Field('Name', value='test')
>>> name_field.as_searchable()
{'test'}
>>> name_field = Field('Age', value=10)
>>> name_field.as_searchable()
{'10'}
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.as_searchable()
{'こんにちは'}
>>> name_field.visible = False
>>> name_field.as_searchable()
set()
>>> EmailField('Email', value='test@testco.com').as_searchable()
{'test@testco.com'}
assign(value)

assign a value to the field

css_class = 'number_field'
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
maxlength = 10
size = 10
units = ''
value = 0
class zoom.fields.MarkdownEditField(label='', *validators, **keywords)

Bases: zoom.fields.EditField

Large markdown edit field

>>> MarkdownEditField('Notes').widget()
'<textarea class="edit_field" height="6" id="notes" name="notes" size="10"></textarea>'
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
class zoom.fields.MarkdownField(label='', *validators, **keywords)

Bases: zoom.fields.MemoField

>>> f = MarkdownField('Notes', value='test **one** 23')
>>> f.display_value()
'<p>test <strong>one</strong> 23</p>'
>>> target = (
...     '<div class="field">\n'
...     '  <div class="field_label">Notes</div>\n'
...     '  <div class="field_edit"><textarea class="memo_field" cols="60" id="notes" name="notes" rows="6" size="10">test **one** 23</textarea></div>\n'
...     '</div>\n'
... )
>>> f.edit() == target
True
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
class zoom.fields.MarkdownText(text)

Bases: object

a markdown text object that can be placed in a form like a field

>>> f = MarkdownText('One **bold** statement')
>>> f.edit()
'<p>One <strong>bold</strong> statement</p>'
edit()

display the markdown as text, even in edit mode

evaluate()

return the value

Not a field so doesn’t return a value

class zoom.fields.MemoField(label='', *validators, **keywords)

Bases: zoom.fields.Field

Edit a paragraph of text.

>>> print(MemoField('Notes').widget())
<textarea class="memo_field" cols="60" id="notes" name="notes" rows="6" size="10"></textarea>
cols = 60
css_class = 'memo_field'
edit()

edit the field

height = 6
rows = 6
show()

show the field

size = 10
value = ''
widget()

returns the field widget

class zoom.fields.MoneyField(label='', *validators, **keywords)

Bases: zoom.fields.DecimalField

Money Field

>>> f = MoneyField("Amount")
>>> f.widget()
'<div class="input-group"><span class="input-group-addon">$</span><input class="decimal_field" type="text" id="amount" maxlength="10" name="amount" size="10" value="" /></div>'
>>> f.display_value()
'$0.00'
>>> f.assign(Decimal(1000))
>>> f.display_value()
'$1,000.00'
>>> from platform import system
>>> l = system()=='Windows' and 'eng' or 'en_GB.utf8'
>>> f = MoneyField("Amount", locale=l)
>>> f.display_value()
'\xa30.00'
>>> f.assign(Decimal(1000))
>>> f.display_value()
'\xa31,000.00'
>>> print(f.show())
<div class="field">
  <div class="field_label">Amount</div>
  <div class="field_show">£1,000.00</div>
</div>
>>> f.widget()
'<div class="input-group"><span class="input-group-addon">£</span><input class="decimal_field" type="text" id="amount" maxlength="10" name="amount" size="10" value="1000" /></div>'
>>> f.units = 'per month'
>>> f.display_value()
'\xa31,000.00 per month'
>>> f.units = ''
>>> f.display_value()
'\xa31,000.00'
>>> f.assign('')
>>> f.display_value()
''
>>> f.assign('0')
>>> f.display_value()
'\xa30.00'
>>> f.assign(' ')
>>> f.display_value()
''
>>> f = MoneyField("Amount", placeholder='0')
>>> f.widget()
'<div class="input-group"><span class="input-group-addon">$</span><input class="decimal_field" type="text" id="amount" maxlength="10" name="amount" placeholder="0" size="10" value="" /></div>'
>>> f = MoneyField("Amount", symbol='£', value=1)
>>> f.widget()
'<div class="input-group"><span class="input-group-addon">£</span><input class="decimal_field" type="text" id="amount" maxlength="10" name="amount" size="10" value="1" /></div>'
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
locale = None
symbol = '$'
widget()

returns the field widget

class zoom.fields.MultiselectField(label='', *validators, **keywords)

Bases: zoom.fields.TextField

Multiselect Field

>>> MultiselectField('Type',value='One',options=['One','Two']).display_value()
'One'
>>> f = MultiselectField('Type', default='One', options=['One','Two'])
>>> f.evaluate()
{'type': []}
>>> f.display_value()
''
>>> print(f.widget())
<select multiple="multiple" class="multiselect" name="type" id="type">
<option value="One" selected>One</option>
<option value="Two">Two</option>
</select>
>>> f.value
>>> f.assign([])
>>> f.value
[]
>>> f.evaluate()
{'type': []}
>>> print(f.widget())
<select multiple="multiple" class="multiselect" name="type" id="type">
<option value="One">One</option>
<option value="Two">Two</option>
</select>
>>> f= MultiselectField('Type',value='One',options=['One','Two'])
>>> print(f.widget())
<select multiple="multiple" class="multiselect" name="type" id="type">
<option value="One" selected>One</option>
<option value="Two">Two</option>
</select>
>>> f = MultiselectField('Type',value=['One','Three'],options=['One','Two','Three'])
>>> print(f.widget())
<select multiple="multiple" class="multiselect" name="type" id="type">
<option value="One" selected>One</option>
<option value="Two">Two</option>
<option value="Three" selected>Three</option>
</select>
>>> f = MultiselectField('Type',default=['One'],options=['One','Two','Three'])
>>> print(f.widget())
<select multiple="multiple" class="multiselect" name="type" id="type">
<option value="One" selected>One</option>
<option value="Two">Two</option>
<option value="Three">Three</option>
</select>
>>> f = MultiselectField('Type',default=['One','Two'],options=['One','Two','Three'])
>>> print(f.widget())
<select multiple="multiple" class="multiselect" name="type" id="type">
<option value="One" selected>One</option>
<option value="Two" selected>Two</option>
<option value="Three">Three</option>
</select>
>>> f = MultiselectField('Type',value='One',options=[('One','uno'),('Two','dos')])
>>> print(f.widget())
<select multiple="multiple" class="multiselect" name="type" id="type">
<option value="uno" selected>One</option>
<option value="dos">Two</option>
</select>
>>> f.value
['uno']
>>> f.evaluate()
{'type': ['uno']}
>>> f.value = ['One']
>>> f.value
['One']
>>> f.evaluate()
{'type': ['uno']}
>>> f.update(**{'type':['dos']})
>>> f.value
['dos']
>>> f.evaluate()
{'type': ['dos']}
>>> f = MultiselectField('Type',value='uno',options=[('One','uno'),('Two','dos')])
>>> f.display_value()
'One'
>>> f = MultiselectField('Type',value='uno',options=[('One','uno'),('One','dos')])
>>> f.display_value()
'One'
>>> print(f.widget())
<select multiple="multiple" class="multiselect" name="type" id="type">
<option value="uno" selected>One</option>
<option value="dos">One</option>
</select>
>>> f = MultiselectField('Type',value=['One','dos'],options=[('One','uno'),('Two','dos')])
>>> f.display_value()
'One; Two'
>>> f.evaluate()
{'type': ['uno', 'dos']}
>>> sorted(f.as_searchable())
['One', 'Two']
>>> f = MultiselectField('Type',value=['One'],options=[('One','uno'),('Two','dos')])
>>> f.display_value()
'One'
>>> f.evaluate()
{'type': ['uno']}
>>> f = MultiselectField('Type', options=[('One','uno'),('Two','dos')])
>>> f.initialize({'type': 'One'})
>>> f.evaluate()
{'type': ['uno']}
>>> f = MultiselectField('Type',value=['uno','dos'],options=[('One','uno'),('Two','dos')])
>>> f.display_value()
'One; Two'
>>> f.evaluate()
{'type': ['uno', 'dos']}
>>> f.option_style('zero','nada')
''
>>> s = lambda label, value: value.startswith('d') and 's1' or 's0'
>>> f = MultiselectField('Type',value=['uno','dos'],options=[('One','uno'),('Two','dos')], styler=s)
>>> print(f.widget())
<select multiple="multiple" class="multiselect" name="type" id="type">
<option class="s0" value="uno" selected>One</option>
<option class="s1" value="dos" selected>Two</option>
</select>
>>> f.styler('test','dos')
's1'
>>> f.option_style('zero','nada')
'class="s0" '

# test for iterating over a string vs. a sequence type (iteration protocol) >>> m1 = MultiselectField(‘Type’, default=‘11’, options=[(‘One’,‘1’),(‘Two’,‘2’),(‘Elves’,‘11’),]).widget() >>> m2 = MultiselectField(‘Type’, default=(‘11’,), options=[(‘One’,‘1’),(‘Two’,‘2’),(‘Elves’,‘11’),]).widget() >>> assert m1 == m2

as_searchable()

Return searchable parts of field

>>> name_field = Field('Name', default='default test')
>>> name_field.as_searchable()
{'default test'}
>>> name_field = Field('Name', value='test')
>>> name_field.as_searchable()
{'test'}
>>> name_field = Field('Age', value=10)
>>> name_field.as_searchable()
{'10'}
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.as_searchable()
{'こんにちは'}
>>> name_field.visible = False
>>> name_field.as_searchable()
set()
>>> EmailField('Email', value='test@testco.com').as_searchable()
{'test@testco.com'}
assign(new_value)

assign a value to the field

css_class = 'multiselect'
default = []
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
evaluate()

Evaluate field value.

Return the value of the field expressed as key value pair (dict) ususally to be combined with other fields in the native type where the value is the native data type for the field type.

option_style(label, value)
styler = None
update(**values)

Update field.

>>> name_field = Field('Name', value='Sam')
>>> name_field.value
'Sam'
>>> name_field.update(city='Vancouver')
>>> name_field.value
'Sam'
>>> name_field.update(name='Joe')
>>> name_field.value
'Joe'
>>> name_field.update(NaMe='Adam')
>>> name_field.value
'Adam'
value = None
widget()

returns the field widget

class zoom.fields.NumberField(label='', *validators, **keywords)

Bases: zoom.fields.TextField

Number Field

>>> NumberField('Size', value=2).display_value()
'2'
>>> NumberField('Size').widget()
'<input class="number_field" type="text" id="size" maxlength="10" name="size" size="10" value="" />'
>>> n = NumberField('Size')
>>> n.assign('2')
>>> n.value
2
>>> n = NumberField('Size', units='units')
>>> n.assign('2,123')
>>> n.value
2123
>>> n.evaluate()
{'size': 2123}
>>> n.display_value()
'2,123 units'
>>> n.assign(None)
>>> n.value == None
True
>>> n.display_value()
''
as_searchable()

Return searchable parts of field

>>> name_field = Field('Name', default='default test')
>>> name_field.as_searchable()
{'default test'}
>>> name_field = Field('Name', value='test')
>>> name_field.as_searchable()
{'test'}
>>> name_field = Field('Age', value=10)
>>> name_field.as_searchable()
{'10'}
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.as_searchable()
{'こんにちは'}
>>> name_field.visible = False
>>> name_field.as_searchable()
set()
>>> EmailField('Email', value='test@testco.com').as_searchable()
{'test@testco.com'}
assign(value)

assign a value to the field

converter

alias of builtins.int

css_class = 'number_field'
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
evaluate()

Evaluate field value.

Return the value of the field expressed as key value pair (dict) ususally to be combined with other fields in the native type where the value is the native data type for the field type.

maxlength = 10
size = 10
units = ''
widget()

returns the field widget

class zoom.fields.PasswordField(label='', *validators, **keywords)

Bases: zoom.fields.TextField

Password Field

>>> PasswordField('Password').show()
''
>>> PasswordField('Password').widget()
'<input class="text_field" id="password" maxlength="40" name="password" size="40" type="password" value="" />'
maxlength = 40
show()

show the field

size = 40
class zoom.fields.PhoneField(label='', *validators, **keywords)

Bases: zoom.fields.TextField

Phone field

>>> PhoneField('Phone').widget()
'<input class="text_field" id="phone" name="phone" size="20" type="text" value="" />'
size = 20
validators = [<zoom.validators.RegexValidator object>]
class zoom.fields.PostalCodeField(label='Postal Code', *validators, **keywords)

Bases: zoom.fields.TextField

Postal code field

>>> PostalCodeField('Postal Code').widget()
'<input class="text_field" id="postal_code" maxlength="7" name="postal_code" size="7" type="text" value="" />'
maxlength = 7
size = 7
class zoom.fields.PulldownField(*a, **k)

Bases: zoom.fields.TextField

Pulldown Field

>>> from zoom.component import Component
>>> PulldownField('Type',value='One',options=['One','Two']).display_value()
'One'
>>> print(PulldownField('Type',value='One',options=['One','Two']).widget())
<select class="pulldown" name="type" id="type">
<option value="One" selected>One</option>
<option value="Two">Two</option>
</select>
>>> print(PulldownField('Type',options=['One','Two']).widget())
<select class="pulldown" name="type" id="type">
<option value=""></option>
<option value="One">One</option>
<option value="Two">Two</option>
</select>
>>> f = PulldownField('Type', options=[('',''),('One',1),('Two',2)])
>>> print(f.widget())
<select class="pulldown" name="type" id="type">
<option value="" selected></option>
<option value="1">One</option>
<option value="2">Two</option>
</select>
>>> f.assign(2)
>>> print(f.widget())
<select class="pulldown" name="type" id="type">
<option value=""></option>
<option value="1">One</option>
<option value="2" selected>Two</option>
</select>
>>> f.assign('2')
>>> print(f.widget())
<select class="pulldown" name="type" id="type">
<option value=""></option>
<option value="1">One</option>
<option value="2" selected>Two</option>
</select>
>>> f = PulldownField('Type', options=[('',''),('One','1'),('Two','2')])
>>> print(f.widget())
<select class="pulldown" name="type" id="type">
<option value="" selected></option>
<option value="1">One</option>
<option value="2">Two</option>
</select>
>>> f.assign(2)
>>> print(f.widget())
<select class="pulldown" name="type" id="type">
<option value=""></option>
<option value="1">One</option>
<option value="2" selected>Two</option>
</select>
>>> f.assign('2')
>>> print(f.widget())
<select class="pulldown" name="type" id="type">
<option value=""></option>
<option value="1">One</option>
<option value="2" selected>Two</option>
</select>
>>> f = PulldownField('Type',value='One',options=[('One','uno'),('Two','dos')])
>>> print(f.widget())
<select class="pulldown" name="type" id="type">
<option value="uno" selected>One</option>
<option value="dos">Two</option>
</select>
>>> f.value
'uno'
>>> f.evaluate()
{'type': 'uno'}
>>> f.value = 'One'
>>> f.value
'One'
>>> f.evaluate()
{'type': 'uno'}
>>> f.update(**{'tYpe':'dos'})
>>> f.value
'dos'
>>> f.evaluate()
{'type': 'dos'}
>>> f = PulldownField('Type',value='uno',options=[('One','uno'),('Two','dos')])
>>> f.display_value()
'One'
>>> f = PulldownField('Type',default='uno',options=[('One','uno'),('Two','dos')])
>>> f.display_value()
'One'
>>> f.evaluate()
{'type': 'uno'}
>>> p = PulldownField('Date', name='TO_DATE', options=[('JAN','jan'), ('FEB','feb'),], default='feb')
>>> p.evaluate()
{'TO_DATE': 'feb'}
>>> p.display_value()
'FEB'
assign(new_value)

assign a value to the field

css_class = 'pulldown'
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
evaluate()

Evaluate field value.

Return the value of the field expressed as key value pair (dict) ususally to be combined with other fields in the native type where the value is the native data type for the field type.

libs = []
select_layout = '<select class="{classed}" name="{name}" id="{name}">\n'
styles = []
value = None
widget()

returns the field widget

class zoom.fields.RadioField(label='', *validators, **keywords)

Bases: zoom.fields.TextField

Radio Field

>>> RadioField('Choice', value='One', values=['One','Two']).display_value()
'One'
>>> print(RadioField('Choice', value='One', values=['One','Two']).widget())
<span class="radio"><input checked class="radio" name="choice" type="radio" value="One" />One</span>
<span class="radio"><input class="radio" name="choice" type="radio" value="Two" />Two</span>
>>> r = RadioField('Choice',value='1',values=[('One','1'),('Two','2')])
>>> print(r.widget())
<span class="radio"><input checked class="radio" name="choice" type="radio" value="1" />One</span>
<span class="radio"><input class="radio" name="choice" type="radio" value="2" />Two</span>
>>> r.assign('1')
>>> r.evaluate()
{'choice': '1'}
>>> r.display_value()
'One'
>>> r.assign('2')
>>> r.evaluate()
{'choice': '2'}
>>> r.display_value()
'Two'
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
values = []
widget()

returns the field widget

class zoom.fields.RangeSliderField(label='', *validators, **keywords)

Bases: zoom.fields.IntegerField

jQuery UI Range Slider

>>> r = RangeSliderField('Price', min=0, max=1500)
>>> r.assign(0)
>>> r.value
(0, 1500)
>>> r.assign((10, 20))
>>> r.value
(10, 20)
>>> r.assign('10,20')
>>> r.value
(10, 20)
>>> isinstance(r.widget(), zoom.Component)
True
>>> isinstance(r.display_value(), str)
True
assign(v)

assign a value to the field

css_class = 'range-slider'
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
js = '\n $( "#%(name)s" ).slider({\n range: true,\n min: %(tmin)s,\n max: %(tmax)s,\n values: [ %(minv)s, %(maxv)s ],\n change: function( event, ui ) {\n var v = ui.values,\n t = v[0] + \',\' + v[1];\n $("input[name=\'%(name)s\']").val(t);\n %(formatter)s\n $( "div[data-id=\'%(name)s\'] span:nth-of-type(1)" ).html( formatter(ui.values[ 0 ]) );\n $( "div[data-id=\'%(name)s\'] span:nth-of-type(2)" ).html( formatter(ui.values[ 1 ]) );\n },\n slide: function( event, ui ) {\n var v = ui.values;\n %(formatter)s\n $( "div[data-id=\'%(name)s\'] span:nth-of-type(1)" ).html( formatter(ui.values[ 0 ]) );\n $( "div[data-id=\'%(name)s\'] span:nth-of-type(2)" ).html( formatter(ui.values[ 1 ]) );\n }\n });\n $("#%(name)s").slider("values", $("#%(name)s").slider("values")); // set formatted label\n '
js_formatter = 'var formatter = function(v) { return v;};'
max = 10
min = 0
show_labels = True
widget()

returns the field widget

class zoom.fields.Section(label, fields, hint='')

Bases: zoom.fields.Fields

A collection of field objects with an associated label.

>>> print(Section('Personal',[TextField('Name',value='Joe')]).show())
<h2>Personal</h2>
<div class="field">
  <div class="field_label">Name</div>
  <div class="field_show">Joe</div>
</div>
edit()
name
render_hint()
show()
class zoom.fields.TextField(label='', *validators, **keywords)

Bases: zoom.fields.Field

Text Field

>>> print(TextField('Name', value="John Doe").show())
<div class="field">
  <div class="field_label">Name</div>
  <div class="field_show">John Doe</div>
</div>
>>> print(TextField('Name',value='John Doe').widget())
<input class="text_field" id="name" maxlength="40" name="name" size="40" type="text" value="John Doe" />
>>> print(TextField('Name',value="Dan").show())
<div class="field">
  <div class="field_label">Name</div>
  <div class="field_show">Dan</div>
</div>
>>> print(TextField('Name',default="Dan").show())
<div class="field">
  <div class="field_label">Name</div>
  <div class="field_show">Dan</div>
</div>
>>> TextField('Name', hint="required").widget()
'<input class="text_field" id="name" maxlength="40" name="name" size="40" type="text" value="" />'
>>> TextField('Name', placeholder="Jack").widget()
'<input class="text_field" id="name" maxlength="40" name="name" placeholder="Jack" size="40" type="text" value="" />'
>>> f = TextField('Title')
>>> f.update(**{"TITLE": "Joe's Pool Hall"})
>>> f.value
"Joe's Pool Hall"
>>> f.evaluate()
{'title': "Joe's Pool Hall"}
css_class = 'text_field'
maxlength = 40
size = 40
widget()

returns the field widget

class zoom.fields.TwitterField(label='', *validators, **keywords)

Bases: zoom.fields.TextField

Twitter field

>>> TwitterField('Twitter').widget()
'<input class="text_field" id="twitter" name="twitter" type="text" value="" />'
>>> TwitterField('Twitter', value='dsilabs').display_value()
'<a target="_window" href="http://www.twitter.com/dsilabs">@dsilabs</a>'
display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
class zoom.fields.URLField(label, *validators, **keywords)

Bases: zoom.fields.TextField

URL Field

>>> URLField('Website', default='www.google.com').display_value()
'<a target="_window" href="http://www.google.com">http://www.google.com</a>'
>>> f = URLField('Website', default='www.google.com')
>>> f.assign('www.dsilabs.ca')
>>> f.display_value()
'<a target="_window" href="http://www.dsilabs.ca">http://www.dsilabs.ca</a>'
>>> f = URLField('Website', default='www.google.com')
>>> f.assign('http://www.dsilabs.ca')
>>> f.display_value()
'<a target="_window" href="http://www.dsilabs.ca">http://www.dsilabs.ca</a>'
>>> f = URLField('Website', default='www.google.com', trim=True)
>>> f.assign('http://www.dsilabs.ca/')
>>> f.display_value()
'<a target="_window" href="http://www.dsilabs.ca">www.dsilabs.ca</a>'
>>> f = URLField('Website', default='www.google.com')
>>> f.assign('https://www.dsilabs.ca/')
>>> f.display_value()
'<a target="_window" href="https://www.dsilabs.ca/">https://www.dsilabs.ca/</a>'
>>> f = URLField('Website', default='www.google.com', trim=True)
>>> f.assign('https://www.dsilabs.ca/')
>>> f.display_value()
'<a target="_window" href="https://www.dsilabs.ca">www.dsilabs.ca</a>'
assign(value)

assign a value to the field

display_value()

Display field value.

>>> name_field = Field('Name', default='default test')
>>> name_field.display_value()
'default test'
>>> name_field = Field('Name', value='test')
>>> name_field.display_value()
'test'
>>> name_field = Field('Name', value='こんにちは')
>>> name_field.display_value()
'こんにちは'
>>> name_field.visible = False
>>> name_field.display_value()
''
maxlength = 120
size = 60
trim = False
zoom.fields.args_to_dict(values=None, **kwargs)

convert args to a dict

Allows developers to pass field values to fields either as a dict or as a set of keyword arguments, whichever makes the most sense for their code.

This is currently only used for clean() but could potentially be used in a number of other places in this modudle where the same pattern shows up. Erring on the side of caution for now.

>>> args_to_dict()
{}
>>> args_to_dict({})
{}
>>> args_to_dict({'name': 'Pat'})
{'name': 'Pat'}
>>> from zoom.utils import pp
>>> pp(args_to_dict(**{'name': 'Pat', 'age': 10}))
{
  "age": 10,
  "name": "Pat"
}
>>> try:
...    args_to_dict({'name': 'Pat'}, 'bad value', age=10)
... except TypeError as e:
...    expected = 'args_to_dict() takes' in str(e)
>>> expected
True
zoom.fields.layout_field(label, content, edit=True)

Layout a field (usually as part of a form).

>>> print(
...     layout_field(
...         'Name',
...         '<input type=text value="John Doe">',
...         True
...     )
... )
<div class="field">
  <div class="field_label">Name</div>
  <div class="field_edit"><input type=text value="John Doe"></div>
</div>
>>> print(layout_field('Name', 'John Doe', False))
<div class="field">
  <div class="field_label">Name</div>
  <div class="field_show">John Doe</div>
</div>
zoom.fields.locate_view(name)

zoom.fill module

fills templates

zoom.fill.dzfill(text, callback)

fill a tag in the <dz: style

zoom.fill.fill(text, callback)

fill a tag in the double handlebars style

>>> def filler(name, *args, **kwargs):
...     if name == 'name':
...         name = kwargs.get('language')=='french' and 'Jacques' or 'James'
...         if 'upper' in args:
...             return name.upper()
...         elif 'lower' in args:
...             return name.lower()
...         else:
...             return name
>>> fill('Hello {{name}}!', filler)
'Hello James!'
>>> fill('Hello {{name language="french"}}!', filler)
'Hello Jacques!'
>>> fill('Hello {{name upper}}!', filler)
'Hello JAMES!'
>>> fill('Hello {{name lower language="french"}}!', filler)
'Hello jacques!'
>>> fill('Hello {{name lower language=french}}!', filler)
'Hello jacques!'
>>> fill('Hello {{name}}!', lambda a: None )
'Hello {{name}}!'
>>> values = {}
>>> fill('Hello {{name}}!', values.get )
'Hello {{name}}!'
>>> fill('Hello {{name "World"}}!', values.get )
'Hello World!'
>>> values['name'] = 'Pat'
>>> fill('Hello {{name "World"}}!', values.get )
'Hello Pat!'
>>> del values['name']
>>> fill('Hello{{name ""}}!', values.get )
'Hello!'

zoom.forms module

zoom.forms

class zoom.forms.Form(*args, **kwargs)

Bases: zoom.fields.Fields

An HTML form

>>> from zoom.fields import TextField
>>> form = Form(TextField("Name"))
>>> print(form.edit())
<form action="" class="clearfix" enctype="application/x-www-form-urlencoded" id="zoom_form" method="POST" name="zoom_form">
<input name="csrf_token" type="hidden" value="<dz:csrf_token>" />
<div class="field">
  <div class="field_label">Name</div>
  <div class="field_edit"><table class="transparent">
    <tr>
        <td nowrap><input class="text_field" id="name" maxlength="40" name="name" size="40" type="text" value="" /></td>
        <td>
            <div class="hint"></div>
        </td>
    </tr>
</table>
</div>
</div>

</form>
edit()
zoom.forms.delete_form(name, cancel=None)

produce a delete form

zoom.forms.form(content=None, **kwargs)

returns the first part of a form

zoom.forms.form_for(*args, **kwargs)

returns a form with optional hidden values

>>> print(form_for('test'))
<form action="<dz:request_path>" class="clearfix" enctype="application/x-www-form-urlencoded" id="zoom_form" method="POST" name="zoom_form">
<input name="csrf_token" type="hidden" value="<dz:csrf_token>" />
test
</form>
zoom.forms.helpers(request)

form helpers

zoom.forms.multipart_form(content, **kwargs)

Returns a multipart form tag.

zoom.forms.multipart_form_for(content, **keywords)

Returns a multipart form tag, surrounding specified content.

zoom.helpers module

zoom.helpers

zoom.helpers.abs_url_for(*a, **k)

calculates absolute url

>>> abs_url_for()
'<dz:abs_site_url><dz:request_path>'
>>> abs_url_for('')
'<dz:abs_site_url><dz:request_path>'
>>> abs_url_for('/')
'<dz:abs_site_url>'
>>> abs_url_for('/', 'home')
'<dz:abs_site_url>/home'
>>> abs_url_for('/home')
'<dz:abs_site_url>/home'
>>> abs_url_for('home')
'<dz:abs_site_url><dz:request_path>/home'
>>> abs_url_for('/user', 1234)
'<dz:abs_site_url>/user/1234'
>>> abs_url_for('/user', 1234, q='test one', age=15)
'<dz:abs_site_url>/user/1234?age=15&q=test+one'
>>> abs_url_for('/user', q='test one', age=15)
'<dz:abs_site_url>/user?age=15&q=test+one'
>>> abs_url_for('/', q='test one', age=15)
'<dz:abs_site_url>?age=15&q=test+one'
>>> abs_url_for(q='test one', age=15)
'<dz:abs_site_url><dz:request_path>?age=15&q=test+one'
>>> abs_url_for('https://google.com', q='test one')
'https://google.com?q=test+one'
zoom.helpers.date()

Returns the current date in text form.

zoom.helpers.include(filename)

Return the included file

produce a link

>>> zoom.system.site = lambda: None
>>> zoom.system.site.url = ''
>>> link_to('Company', 'http://company.com')
'<a href="http://company.com" name="link-to-company">Company</a>'
>>> link_to('http://company.com')
'<a href="http://company.com" name="link-to-http-company-com">http://company.com</a>'
>>> link_to('http://company.com', q='test')
'<a href="http://company.com?q=test" name="link-to-http-company-com">http://company.com</a>'
zoom.helpers.lorem()

Returns some sample latin text to use for prototyping.

zoom.helpers.mail_to(name, *args, **kwargs)

produce an email link

>>> mail_to('Tester', 'test@testco.com')
'<a href="test@testco.com">Tester</a>'
>>> mail_to('test@testco.com')
'<a href="test@testco.com">test@testco.com</a>'
zoom.helpers.owner_email()

Returns the email address of the site owner.

Returns a link for the site owner.

zoom.helpers.owner_name()

Returns the name of the site owner.

zoom.helpers.owner_url()

Returns the URL of the site owner.

zoom.helpers.tag_for(name, *a, **k)

create a zoom tag

>>> tag_for('name')
'<dz:name>'
>>> tag_for('name', default=1)
'<dz:name default=1>'
zoom.helpers.upper(text)

Returns the given text in upper case.

zoom.helpers.url_for(*a, **k)

creates urls

>>> zoom.system.site = lambda: None
>>> zoom.system.site.url = ''
>>> url_for()
''
>>> url_for('')
''

# >>> url_for(‘/’) # ‘<dz:site_url>’

>>> url_for('/', 'home')
'/home'
>>> url_for('/home')
'/home'
>>> url_for('home')
'home'
>>> url_for('/user', 1234)
'/user/1234'
>>> url_for('/user', 1234, q='test one', age=15)
'/user/1234?age=15&q=test+one'
>>> url_for('/user', q='test one', age=15)
'/user?age=15&q=test+one'
>>> url_for('/', q='test one', age=15)
'?age=15&q=test+one'
>>> url_for(q='test one', age=15)
'?age=15&q=test+one'
>>> url_for('https://google.com', q='test one')
'https://google.com?q=test+one'
zoom.helpers.url_for_item(*args, **kwargs)

returns a url for an item on the curren page

>>> zoom.system.request = lambda: None
>>> zoom.system.request.route = ['myapp', 'mypage']
>>> url_for_item()
'/myapp/mypage'
>>> url_for_item(100)
'/myapp/mypage/100'
zoom.helpers.url_for_page(*args, **kwargs)

returns a url for a page of the current app

>>> url_for_page()
'<dz:app_url>'
>>> url_for_page('page1')
'<dz:app_url>/page1'
zoom.helpers.username()

Returns the username.

zoom.helpers.when(date, since=None)

Formats a date

>>> now = datetime.datetime(2018, 2, 24, 1, 2, 42)
>>> when(now - zoom.tools.one_day * 2, now)
'<span title="2018-02-22 01:02:42">2 days ago</span>'
zoom.helpers.who(user_id)

Formats a user_id

>>> zoom.system.site = site = zoom.sites.Site()
>>> zoom.system.user = site.users.first(username='admin')
>>> user_id = site.users.first(username='guest').user_id
>>> zoom.system.user.is_admin = True
>>> who(user_id)
'<a href="/admin/users/guest" name="link-to-guest">guest</a>'
>>> who(100)
'unknown (100)'
>>> zoom.system.user.is_admin = False
>>> who(user_id)
'guest'
>>> who(100)
'unknown'
zoom.helpers.year()

Returns the current year in text form.

zoom.html module

zoom.html

zoom.html.a(content, *args, **kwargs)

generate an anchor tag

>>> a('home', href='/home')
'<a href="/home">home</a>'
zoom.html.div(*content, **kwargs)

generates an HTML div tag

Content can be any number of items that support str conversion. Named arguments are used as tag attributes for the div tag.

>>> div('some content')
'<div>some content</div>'
>>> div('some', ' content')
'<div>some content</div>'
>>> div('')
'<div></div>'
>>> div(Class='header')
'<div class="header"></div>'
zoom.html.glyphicon(icon, **kwargs)

generates a glpyhicon span

>>> glyphicon('heart')
'<span aria-hidden="true" class="glyphicon glyphicon-heart"></span>'
>>> glyphicon('heart', Class="special")
'<span aria-hidden="true" class="glyphicon glyphicon-heart special"></span>'
>>> t = (
...     '<span aria-hidden="true" class="glyphicon '
...     'glyphicon-heart special" style="color:red"></span>'
... )
>>> glyphicon('heart', Class="special", style="color:red") == t
True
zoom.html.h1(text)

h1 tag

>>> h1('my heading')
'<h1>my heading</h1>'
zoom.html.h2(text)

h2 tag

>>> h2('my subheading')
'<h2>my subheading</h2>'
zoom.html.h3(text)

h3 tag

>>> h3('my subsubheading')
'<h3>my subsubheading</h3>'
zoom.html.hidden(*args, **kwargs)
zoom.html.img(src, **kwargs)

HTML Image Tag

>>> img('/static/images/no_image.png', typed='standard-image')
'<img src="/static/images/no_image.png" type="standard-image" />'
zoom.html.input(*args, **kwargs)
zoom.html.li(items)

generate list items

>>> li(['this','that'])
'<li>this</li><li>that</li>'
zoom.html.ol(items)

generate an ordered list

>>> ol(['this','that'])
'<ol><li>this</li><li>that</li></ol>'
zoom.html.pre(content)
zoom.html.span(content='', **kwargs)

generates an div tag

>>> span('some content')
'<span>some content</span>'
>>> span('')
'<span></span>'
>>> span(Class='header')
'<span class="header"></span>'
zoom.html.table(rows)

returns rows wrapped in an HTML table

zoom.html.tag(element, *args, **kwargs)

generates an HTML tag

>>> tag('div', 'some content', classed='content-card')
'<div class="content-card">some content</div>'
>>> tag('a', href='http://www.google.com')
'<a href="http://www.google.com"></a>'
zoom.html.ul(items)

generate an unordered list

>>> ul(['this','that'])
'<ul><li>this</li><li>that</li></ul>'

zoom.instances module

zoom.instances

Mananage zoom instances

Note: Experimental!

class zoom.instances.Instance(path=None)

Bases: object

Zoom Instance

A Zoom instance is a directory containing sites, apps and themes. Zoom can host multiples sites using the same configuration under a single Instance.

>>> import tempfile
>>> instance_path = os.path.join(tempfile.gettempdir(), 'fakeinstance')
>>> instance = Instance(instance_path)
>>> instance.create()
>>> os.path.exists(instance.path)
True
>>> os.path.exists(os.path.join(instance.path, 'sites'))
True
>>> instance.sites == {}
True
>>> instance.destroy()
>>> os.path.exists(instance.path)
False
create()

Create a new instance

destroy()

Destroy an empty instance

run_background_jobs()

Run background jobs

Iterates through the sites in the instance and calls run_background_jobs on each one.

>>> instance = Instance()
>>> instance.run_background_jobs()
localhost
sites

a dict of sites for the instance

>>> instance = Instance()
>>> print(instance.sites)
{'localhost': Site('localhost')}
>>> import tempfile
>>> instance_path = os.path.join(tempfile.gettempdir(), 'fakeinstance')
>>> instance = Instance(instance_path)
>>> got_it = False
>>> try:
...     instance.sites
... except InstanceMissingException:
...     got_it = True
>>> got_it
True
sites_path

the path to the sites of the instance

>>> instance_directory = zoom.tools.zoompath('web')
>>> instance = Instance(instance_directory)
>>> instance.sites_path == instance_directory + '/sites'
True
exception zoom.instances.InstanceExistsException

Bases: Exception

Instance directory exists

exception zoom.instances.InstanceMissingException

Bases: Exception

Instance directory is missing

zoom.jsonz module

zoom.jsonz

JSON with extra converters

zoom.jsonz.dumps(data, *a, **k)

Convert to json with support for date and decimal types

>>> dumps('test')
'"test"'
>>> loads(dumps('test'))
'test'
>>> loads(dumps(date(2015,1,1)))
datetime.date(2015, 1, 1)
>>> loads(dumps(Decimal('20.40')))
Decimal('20.40')
zoom.jsonz.loads(text)

load JSON from a string

zoom.logging module

zoom.logging

class zoom.logging.LogHandler(request, level=20)

Bases: logging.Handler

Log handler

Logs information to the log table in the system database.

emit(record)

Do whatever it takes to actually log the specified logging record.

This version is intended to be implemented by subclasses and so raises a NotImplementedError.

zoom.logging.add_entry(request, status, entry)

Add an entry to the system log

zoom.logging.handler(request, handler, *rest)

Handles logging

>>> import zoom.request
>>> import zoom.profiler
>>> request = zoom.request.build('http://localhost')
>>> request.profiler = zoom.profiler.SystemTimer(request.start_time)
>>> request.site = zoom.sites.Site()
>>> def log_something(request):
...     logger = logging.getLogger(__name__)
...     logger.debug('hey')
>>> response = handler(request, log_something)
>>> response is None
True
zoom.logging.log_activity(message, *args, **kwargs)

Log user activity

Use for high level user activity logging, such as editing records.

zoom.mail module

zoom.mail

email services

zoom.mail.send(recipients, subject, message, attachments=None)

send an email

zoom.mail.send_as(sender, recipients, subject, message, attachments=None)

send an email as a specific sender

zoom.mail.deliver()

deliver mail

class zoom.mail.Attachment(pathname, data=None, mime_type=None)

Bases: object

Email attachment

provide either a pathname, or a filename and a pathname, or if sending directly a filename and a file-like object.

as_tuple()

partilars required for delivery

read

provides a reader for the data

if the data is not open, it will be because the user provided only a pathanme so we open the file at the pathname and return it

zoom.middleware module

zoom.middleware

A set of functions that can be placed between the HTTP server and the application layer. These functions can provide various services such as content serving, caching, error trapping, security, etc..

zoom.middleware.capture_stdout(request, handler, *rest)

Capture printed output for debugging purposes

>>> request = zoom.request.build('http://localhost')
>>> def get_content(request):
...     print('hey')
...     return zoom.response.HTMLResponse('I said {*stdout*}')
>>> response = capture_stdout(request, get_content)
>>> response.content
'I said hey\n'
zoom.middleware.check_csrf(request, handler, *rest)

Check csrf token

>>> zoom.system.providers = []
>>> data = dict(csrf_token='1234', name='Pat')
>>> request = zoom.request.build('http://localhost/', data)
>>> request.session = zoom.utils.Bunch()
>>> request.site = zoom.sites.Site()
>>> result = check_csrf(request, lambda a: False)
>>> isinstance(result, zoom.response.RedirectResponse)
True
>>> data = dict(csrf_token='1234', name='Pat')
>>> request = zoom.request.build('http://localhost/', data)
>>> request.session = zoom.utils.Bunch(csrf_token='4321')
>>> request.site = zoom.sites.Site()
>>> result = check_csrf(request, lambda a: False)
>>> isinstance(result, zoom.response.RedirectResponse)
True
>>> data = dict(csrf_token='1234', name='Pat')
>>> request = zoom.request.build('http://localhost/', data)
>>> request.session = zoom.utils.Bunch(csrf_token='1234')
>>> request.site = zoom.sites.Site()
>>> result = check_csrf(request, lambda a: False)
>>> isinstance(result, zoom.response.RedirectResponse)
False
zoom.middleware.debug(request)

Debugging page

>>> type(debug(zoom.request.build('http://localhost/')))
<class 'zoom.response.HTMLResponse'>
zoom.middleware.display_errors(request, handler, *rest)

Display errors for developers

>>> request = zoom.request.build('http://localhost')
>>> request.app = zoom.utils.Bunch(theme='default', templates_paths=[])
>>> request.host = 'localhost'
>>> request.site = zoom.sites.Site()
>>> request.site.theme = 'default'
>>> request.site.request = request
>>> zoom.system.request = request
>>> zoom.system.site = request.site
>>> zoom.system.user = request.site.users.first(username='admin')
>>> zoom.system.user.is_admin = True
>>> def throw(request):
...     raise Exception('ouch!')
>>> response = display_errors(request, throw)
>>> isinstance(response, HTMLResponse)
True
>>> zoom.system.user.is_admin = False
>>> response = display_errors(request, throw)
>>> isinstance(response, HTMLResponse)
True
zoom.middleware.get_csrf_token(session)

generate a csrf token

>>> zoom.system.request.session = session = zoom.utils.Bunch()
>>> hasattr(session, 'csrf_token')
False
>>> bool(get_csrf_token(session))
True
>>> hasattr(session, 'csrf_token')
False
>>> _ = zoom.forms.form_for('test')
>>> hasattr(session, 'csrf_token')
True
zoom.middleware.handle(request, handlers=None)

handle a request

zoom.middleware.not_found(request)

return a 404 page for site

>>> zoom.system.site = site = zoom.sites.Site()
>>> site.theme = 'default'
>>> site.title = 'My Site'
>>> request = zoom.request.build('http://localhost')
>>> request.app = zoom.utils.Bunch(theme='default')
>>> request.site = site
>>> response = not_found(request)
>>> response.status
'404 Not Found'
zoom.middleware.reset_modules(request, handler, *rest)

reset the modules to a known starting set

Memorizes the modules in use on the first round. Then on every subsequent round, it removes any extra modules before passing the request on.

>>> def loader(request):
...     import zoom.audit
>>> def not_a_loader(request):
...     pass
>>> if 'zoom.audit' in sys.modules:
...     del sys.modules['zoom.audit']
>>> fresh = list(sys.modules)
>>> reset_modules(zoom.request.build('http://localhost'), loader)
>>> fresh == list(sys.modules)
False
>>> reset_modules(zoom.request.build('http://localhost'), not_a_loader)
>>> fresh == list(sys.modules)
True
zoom.middleware.serve_favicon(request, handler, *rest)

Serve a favicon file

This function only handles the case where the favicon reference is /favicon.ico, as would be typical in a local dev environment. For production environments the favicon would typically be either part of the theme or would be stored statically, both of which would typically be served up by your proxy or your web server depending on your config.

If your web sever is not configured to handle static or themes for you, Zoom will happily serve them up via the serve_theme or serve_static middleware found elsewhere in this module.

>>> from zoom.request import Request
>>> def content_handler(request, *rest):
...     return '200 OK', [], 'nuthin'
>>> request = Request(
...     dict(REQUEST_URI='/favicon.ico'),
... )
>>> response = serve_favicon(
...     request,
...     content_handler,
... )
>>> isinstance(response, ICOResponse)
True
>>> request = Request(
...     dict(REQUEST_URI='/'),
... )
>>> status, _, content = serve_favicon(
...     request,
...     content_handler,
... )
>>> content
'nuthin'
zoom.middleware.serve_html(request, handler, *rest)

Serve HTML from the Content app

Direct a request for an HTML page to the content app

>>> site = zoom.sites.Site()
>>> url = 'http://localhost/index.html'
>>> request = zoom.request.build(url)
>>> request.path == '/index.html'
True
>>> response = serve_html(request, lambda a: None)
>>> request.path == '/content/index.html'
True
>>> url = 'http://localhost/index.css'
>>> request = zoom.request.build(url)
>>> request.path == '/index.css'
True
>>> response = serve_html(request, lambda a: None)
>>> request.path == '/index.css'
True
zoom.middleware.serve_images(request, handler, *rest)

Serve an image file

>>> url = 'http://localhost/images/banner_logo.png'
>>> request = zoom.request.build(url)
>>> request.site = zoom.sites.Site()
>>> result = serve_images(request, lambda a: False)
>>> isinstance(result, PNGResponse)
True
>>> url = 'http://localhost/notimages/banner_logo.png'
>>> request = zoom.request.build(url)
>>> serve_images(request, lambda a: False)
False
zoom.middleware.serve_redirects(request, handler, *rest)

Serves redirects

>>> request = zoom.request.build('http://localhost')
>>> result = serve_redirects(request, lambda a: False)
>>> print(result)
False
>>> request.site = zoom.utils.Bunch(abs_url='http://localhost')
>>> redirect_home = zoom.response.RedirectResponse('/home')
>>> result = serve_redirects(request, lambda a: redirect_home)
>>> print(result.headers)
OrderedDict([('Location', '/home')])
zoom.middleware.serve_response(*path)

Serve up various respones with their correct response type

>>> zoom_js = zoom.tools.zoompath('web/www/static/zoom/zoom.js')
>>> response = serve_response(zoom_js)
>>> isinstance(response, JavascriptResponse)
True
>>> zoom_path = zoom.tools.zoompath('web')
>>> response = serve_response(zoom_path, 'www/static/zoom/nada.js')
>>> isinstance(response, JavascriptResponse)
False
>>> response.content
"file not found: 'www/static/zoom/nada.js'"
>>> response.status
'404 Not Found'
>>> zoom_path = zoom.tools.zoompath('web')
>>> response = serve_response(zoom_path, 'www/static/zoom/images')
>>> isinstance(response, JavascriptResponse)
False
>>> response.content
"unknown file type ''"
>>> response.status
'415 Unsupported Media Type'
zoom.middleware.serve_static(request, handler, *rest)

Serve a static file

Static files can be served in serveral ways. This particular middleware intended to be inserted into the middleware stack near the front before most other things. The reason being is that in many cases the static files are not site specific or app specific but rather are shared by the entire instance. In addition, many times static files require no authorization or special rights. This means that in many cases static files can be served without knowing who is asking or what site they are for. That is what this middleware does.

This handler is mainly intended for development purposes as in a production evironment this type of content would typically be served up by the HTTP server or a caching layer.

Note: This layer looks in four separate places for static files to serve. Three related to the zoom instance, and one, as a last resort, from the zoom library itself. If you are concerned about exposing the wrong files, be careful what you put in those directories or better yet, use a proxy.

In addition to this instance wide static serving Sites and Apps may implement static file serving. If they do, this layer is unaware of that fact.

The locations this handler will look are relative to the zoom instance. They are:

<instance>/static <instance>/www/static <instance>/../static zoom/web/www/static
>>> url = 'http://localhost/static/zoom/zoom.js'
>>> request = zoom.request.build(url)
>>> result = serve_static(request, lambda a: False)
>>> isinstance(result, JavascriptResponse)
True
>>> url = 'http://localhost/notstatic/zoom/zoom.js'
>>> request = zoom.request.build(url)
>>> result = serve_static(request, lambda a: False)
>>> isinstance(result, JavascriptResponse)
False
zoom.middleware.serve_themes(request, handler, *rest)

Serve a theme file

>>> url = 'http://localhost/themes/default/css/style.css'
>>> request = zoom.request.build(url)
>>> request.site = zoom.sites.Site()
>>> result = serve_themes(request, lambda a: False)
>>> isinstance(result, CSSResponse)
True
>>> url = 'http://localhost/notthemes/default/default.html'
>>> request = zoom.request.build(url)
>>> serve_themes(request, lambda a: False)
False
zoom.middleware.trap_errors(request, handler, *rest)

Trap exceptions and raise a server error

>>> def exception_handler(request, *rest):
...     raise Exception('error!')
>>> def content_handler(request, *rest):
...     return HTMLResponse('nuthin')
>>> request = {}
>>> response = trap_errors(request, content_handler)
>>> status, headers, content = response.as_wsgi()
>>> content
b'nuthin'
>>> status
'200 OK'
>>> response = trap_errors(request, exception_handler)
>>> status, headers, content = response.as_wsgi()
>>> status
'500 Internal Server Error'
>>> 'Exception: error!' in str(content)
True

zoom.migrations module

zoom.migrations

Work in progress - experimental

class zoom.migrations.Migration(db)

Bases: object

Provies the methods to apply or revert transformations to get system to a desired state.

apply()

apply changes to the database

name
revert()

Revert changes to the database

class zoom.migrations.Migrations(db, steps)

Bases: object

performs the migration steps needed to get to the target version

Systems are migrated from one state to another by providing a sequence of steps typically subclassed from the Migration class. Migrations should provide both an apply method to peform a transformation to take a system to a desired state and, where possible, a revert method to undo the transformation to transform the sytem back to its original state.

As they are applied and reverted, the migrations are tracked in a data store including the name of the object peforming the migration ( usually a subclass of Migration) the version of the system that was attained by applying or reverting the migration, the revision number of the migration (an integer representing the application or reversion of a transformation), the method used (apply or revert) and the timestamp of when the migration took place.

>>> class AddFaxColumnToUser(Migration):
...     def apply(self):
...         self.db('alter table users add column fax char(30)')
...     def revert(self):
...         self.db('alter table users drop column fax')
>>> steps = [
...     StartMigration,
...     AddFaxColumnToUser,
... ]
>>> zoom.system.site = site = zoom.sites.Site()
>>> site.db = zoom.database.setup_test()
>>> print(site.db('describe users'))
Field      Type             Null Key Default Extra
---------- ---------------- ---- --- ------- --------------
id         int(10) unsigned NO   PRI None    auto_increment
username   char(50)         NO   UNI None
password   varchar(125)     YES      None
first_name char(40)         YES      None
last_name  char(40)         YES      None
email      char(60)         YES  MUL None
phone      char(30)         YES      None
created    datetime         YES      None
updated    datetime         YES      None
last_seen  datetime         YES  MUL None
created_by int(10) unsigned YES      None
updated_by int(10) unsigned YES      None
status     char(1)          YES      None
>>> migrations = Migrations(site.db, steps)
>>> migrations.migrate()
>>> print(site.db('describe users'))
Field      Type             Null Key Default Extra
---------- ---------------- ---- --- ------- --------------
id         int(10) unsigned NO   PRI None    auto_increment
username   char(50)         NO   UNI None
password   varchar(125)     YES      None
first_name char(40)         YES      None
last_name  char(40)         YES      None
email      char(60)         YES  MUL None
phone      char(30)         YES      None
created    datetime         YES      None
updated    datetime         YES      None
last_seen  datetime         YES  MUL None
created_by int(10) unsigned YES      None
updated_by int(10) unsigned YES      None
status     char(1)          YES      None
fax        char(30)         YES      None
>>> migrations.migrate(0)
>>> print(site.db('describe users'))
Field      Type             Null Key Default Extra
---------- ---------------- ---- --- ------- --------------
id         int(10) unsigned NO   PRI None    auto_increment
username   char(50)         NO   UNI None
password   varchar(125)     YES      None
first_name char(40)         YES      None
last_name  char(40)         YES      None
email      char(60)         YES  MUL None
phone      char(30)         YES      None
created    datetime         YES      None
updated    datetime         YES      None
last_seen  datetime         YES  MUL None
created_by int(10) unsigned YES      None
updated_by int(10) unsigned YES      None
status     char(1)          YES      None
>>> migrations.revisions.zap()
migrate(target=None)

migrate to a target version

Target is the desired version. If no target is supplied the migrations required to get to the most up to date version are applied.

class zoom.migrations.StartMigration(db)

Bases: zoom.migrations.Migration

Start Migration

This is a starting state migration. It serves as a starting point for the system before any migrations were applied and does not itself apply any data transformations. This migration should be used as the first element of all migration sequences.

In theory, migrating back to this state should put the database back into its original state. By “in theory” we mean that assuming all of the applied migrations are completely revertable which may not be the case in all cases.

class zoom.migrations.SystemMigrationRecord

Bases: zoom.utils.Record

Migration Record

zoom.models module

zoom.models

common models

zoom.models.Attachment

alias of zoom.models.SystemAttachment

class zoom.models.Group

Bases: zoom.utils.Record

Zoom Users Group

>>> zoom.system.site = site = zoom.sites.Site()
>>> groups = Groups(site.db)
>>> group = groups.first(name='users')
>>> user = site.users.first(username='admin')
>>> group.allows(user, 'edit')
True
>>> group.key
'2'
>>> group.url
'/admin/groups/2'
>>> group.link
'<a href="/admin/groups/2" name="link-to-users">users</a>'
>>> group.roles
{4}
>>> zoom.utils.pp(group.apps)
{
  10,
  12,
  20,
  28,
  29
}
>>> groups.first(name='everyone').subgroups
{2, 3}
>>> groups.first(name='users').user_ids
[2]
>>> {u.username for u in site.users.get(groups.first(name='users').user_ids)}
{'user'}
add_user(user)
allows(user, action)
apps

Return set of apps that group can access

get_users()
key

user as link

roles
subgroups

Return set of subgroups that are part of this group

url

user view url

user_ids

Return list of user IDs of users that are in the group

users

Return list of users that are part of this group

class zoom.models.Groups(db, entity=<class 'zoom.models.Group'>)

Bases: zoom.records.RecordStore

locate(locator)

locate a group whether it is referred to by reference, id or name

class zoom.models.Member

Bases: zoom.utils.Record

class zoom.models.Members(db, entity=<class 'zoom.models.Member'>)

Bases: zoom.records.RecordStore

class zoom.models.Model

Bases: zoom.utils.DefaultRecord

Model Superclass

Provide basic model properties and functions.

Subclass this to create a Model that can be stored in a RecordStore, EntityStore or some other type of store.

Assumes every record has an id attribute. If not, you will need to provide one via an additional property defined in the subclass.

The key can end up being just the str of the id, however it is provided separately to make it easy to provide human friendly keys typically used in REST style URLs. If used this way the key should generated such that it is unique for each record.

>>> zoom.system.site = site = zoom.sites.Site()
>>> zoom.system.request = zoom.utils.Bunch(route=[])
>>> class MyModel(Model):
...     pass
>>> thing = MyModel(name='Pat Smith')
>>> thing.name
'Pat Smith'
>>> thing.key
'pat-smith'
>>> url_for_item('pat-smith')
'/pat-smith'
>>> thing.url
'/pat-smith'
>>> thing.link
'<a href="/pat-smith" name="link-to-pat-smith">Pat Smith</a>'
>>> thing.allows('user', 'edit')
False
allows(user, action)
key

Return the key

Return a link

url

Return a valid URL

class zoom.models.SystemAttachment

Bases: zoom.utils.Record

zoom.models.get_users(db, group)

Get users of a Group

Gets the users that are members of a group from a given database.

>>> site = zoom.sites.Site()
>>> users_group = Groups(site.db).first(name='users')
>>> get_users(site.db, users_group)
{2}
zoom.models.handler(request, handler, *rest)

zoom.mvc module

zoom.mvc

classes to support the model, view, controller pattern

class zoom.mvc.Controller(model=None, **kwargs)

Bases: zoom.mvc.Dispatcher

Controls a Model

Use this class when an action is going to change the state of the model.

class zoom.mvc.Dispatcher(model=None, **kwargs)

Bases: object

dispatches actions to a method

Accepts incoming user input actions and calls the appropriate method to handle the request. Unlike the Controller and the View, the Dispatcher doesn’t alter the incoming input in any way, but rather passes it along verbatim to the method handling the request.

>>> class MyDispatcher(Dispatcher):
...     def add(self, a, b):
...         return a + b
>>> dispatcher = MyDispatcher()
>>> dispatcher('add', 1, 2)
3
home = None
class zoom.mvc.DynamicView(model=None, **k)

Bases: zoom.mvc.View

Dynamic View - experimental (may change)

A decorator class that provides views of objects dynamically loading its own templates in the process.

Within templates the object being decorated is referred to as self. Any attribtues or properties can be simply accessed using self.<name> for whatever the name is. Templates are rendered using python format() function so object structures can be taversed in the usual way within templates.

The object optionally passed as the first parameter upon construction is referred to as self.model. Additional objects can be added as keyword parameters, which can then also be referenced with self.<name>.

asset_types = ['html', 'css', 'js']
fill_js(script, obj)

Fill js tags

DynamicView object attributes and properties can be accessed from their accompanying .js content via a {self.<name>} reference.

This method is responsible for filling in these tags and can be overridden by subclasses of DynamicView if a different behaviour is desired.

get_assets(name=None)

Get view assets

index()

return the default rendered view

render(view=None)

Render the view

class zoom.mvc.View(model=None, **kwargs)

Bases: zoom.mvc.Dispatcher

Views a model

Use to view a model without altering it.

>>> class MyView(View):
...     def index(self):
...         return 'index page'
...     def show(self, item):
...         return 'showing %s' % item
...     def throw(self, item):
...         raise Exception('thrown')
>>> view = MyView()
>>> view()
'index page'
>>> view('100')
'showing 100'
>>> view('100', data=dict(d='extra'))
'showing 100'
>>> thrown = False
>>> try:
...     view('throw')
... except Exception:
...     thrown = True
>>> thrown
True
show(*args, **kwargs)

View a specific item (stub)

zoom.mvc.as_attr(text)

Replace hyphens with underscores

>>> as_attr('this-page')
'this_page'
zoom.mvc.dispatch(*args)

Create and call dispatchers in order

Returns a function that will handle a request by trying each argument in succession. If the argument is a Dispatcher it will be created before being called. If it is a callable, it will be called as-is. As soon as one of them returns a response we exit. If none of the returns a response we return None, which generally results in a 404.

>>> class MyView(View):
...     def index(self):
...         return 'home page'
...     def show(self, key):
...         return 'showing %s' % key
>>> main = zoom.dispatch(MyView)
>>> main((), zoom.utils.Bunch(data={}))
'home page'
>>> main(('100',), zoom.utils.Bunch(data={}))
'showing 100'
zoom.mvc.evaluate(obj, name, route, data)

Get the value of an attribute

>>> thing = zoom.utils.Bunch(name='Thing', show=lambda a, name: 'showing %s' % name)
>>> route, data = ('app',), {'name': 'one'}
>>> evaluate(thing, 'name', route, data)
'Thing'
>>> evaluate(thing, 'show', route, data)
'showing one'
zoom.mvc.remove_buttons(data)

Remove buttons from input data

>>> data = dict(name='Pat', age=20, save_button='Save')
>>> zoom.utils.pp(remove_buttons(data))
[
  {
    "save_button": "Save"
  },
  {
    "age": 20,
    "name": "Pat"
  }
]

zoom.packages module

zoom.packages

Provide a simple shorthand way to include external components in projects.

zoom.packages.get_registered_packages()

Returns the list of packages known to the site

>>> import zoom.request
>>> zoom.system.request = zoom.request.Request(dict(PATH_INFO='/'))
>>> zoom.system.site = zoom.site.Site(zoom.system.request)
>>> zoom.system.request.app = zoom.utils.Bunch(packages={})
>>> packages = get_registered_packages()
>>> 'c3' in packages
True
zoom.packages.load(pathname)

Load a packages file into a dict

zoom.packages.requires(*package_names)

Inform framework of the packages required for rendering

>>> import zoom.request
>>> request = zoom.request.Request(dict(PATH_INFO='/'))
>>> zoom.system.site = zoom.site.Site(request)
>>> zoom.system.parts = zoom.Component()
>>> requires('c3')
>>> libs = zoom.system.parts.parts['libs']
>>> print('\n'.join(list(libs)))
https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.17/d3.min.js
https://cdnjs.cloudflare.com/ajax/libs/c3/0.4.15/c3.min.js
>>> zoom.system.parts = zoom.Component()
>>> requires('jquery-ui')
>>> libs = zoom.system.parts.parts['libs']
>>> print('\n'.join(list(libs)))
//code.jquery.com/jquery-3.3.1.min.js
//code.jquery.com/ui/1.12.1/jquery-ui.min.js
>>> try:
...     requires('d4')
... except Exception as e:
...     'Missing required' in str(e) and 'raised!'
'raised!'

zoom.page module

zoom.page

class zoom.page.ClearSearch(model=None, **k)

Bases: zoom.mvc.DynamicView

class zoom.page.Page(content, *args, **kwargs)

Bases: object

a web page

header()

return page header

helpers(request)

provide page helpers

render(request)

render page

class zoom.page.PageHeader(model=None, **k)

Bases: zoom.mvc.DynamicView

page header

action_items
search_box
subtitle
title
class zoom.page.SearchBox(model=None, **k)

Bases: zoom.mvc.DynamicView

clear
clear_url = '<dz:request_path>/clear'
request_path = '<dz:request_path>'
zoom.page.page

alias of zoom.page.Page

zoom.profiler module

performance profiler

class zoom.profiler.SystemTimer(start_time=None)

Bases: object

time system events

>>> timer = SystemTimer()
>>> timer.add('got here')
>>> timer.add('got there')
>>> import time
>>> timer.time('slow step', time.sleep, 0.1)
>>> report = timer.report()
>>> len(report.splitlines())
7
>>> len(timer.data)
5
add(comment)

add a measure to the system timer log

data
labels
report()

print a report of the timed events

time(name, function, *args, **kwargs)
zoom.profiler.get_profile_data(profiler)

Capture the stdout printout of the code profiler

>>> class Profiler(object):
...     def print_stats(self):
...         print('the stats!')
>>> get_profile_data(Profiler())
'the stats!\n'
zoom.profiler.handler(request, handler, *rest)

Handle profiled requests

zoom.profiler.profiled(request, handler, *rest)
zoom.profiler.round(value)

Round a decimal value

>>> round(Decimal(1.23422))
Decimal('1.234')

zoom.queues module

zoom.queues

message queues

class zoom.queues.Queues(db=None)

Bases: object

messages

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> t.put('hey!')
1
>>> t.put('you!')
2
>>> t.peek()
'hey!'
clear()
get(name, newest=None)
stats()
topic(name, newest=None)
topics()
class zoom.queues.Topic(name, newest=None, db=None)

Bases: object

message topic

call(*messages, delay=0.1, timeout=15)

send messages and wait for responses

clear()

clear the topic

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> t.send('hey!', 'you!')
[1, 2]
>>> len(t)
2
>>> t.clear()
>>> len(t)
0
handle(f, timeout=0, delay=0.1, one_pass=False)

respond to and consume messages

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> def echo(m):
...     if m == 'quit': raise StopHandling
...     print('got', repr(m))
>>> t.put('hey!')
1
>>> t.put('you!')
2
>>> t.put('quit')
3
>>> t.handle(echo)
got 'hey!'
got 'you!'
2
join(jobs, delay=0.1, timeout=15)

wait for responses for consumers

NOTE: the assumption at this point is that any provided delay or timeout applies to all jobs. If jobs need varying arguments then mutliple calls to join should be considered.

last()

get row_id of the last (newest) message in the topic

len(newest=None)

return the number of messages in the topic

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> t.put('hey!')
1
>>> t.put('you!')
2
>>> t.len()
2
listen(f, delay=0.1, meta=False)

observe but don’t consume messages

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> t.put('hey!')
1
>>> t.put('you!')
2
>>> def echo(m):
...     print(m)
...     return m == 'you!'
>>> t.listen(echo)
hey!
you!
2
>>> t1 = messages.topic('test_topic1')
>>> t2 = messages.topic('test_topic2')
>>> t3 = messages.topic(None)
>>> t1.put('hey!')
3
>>> t2.put('you!')
4
>>> def echo(m):
...     print(m)
...     return m == 'you!'
>>> t3.listen(echo)
hey!
you!
2
peek(newest=None)

return the next message but don’t remove it

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> t.peek()
>>> t.put('hey!')
1
>>> t.put('you!')
2
>>> t.peek()
'hey!'
>>> t.peek()
'hey!'
perform(task, *args, **kwargs)

consume a single message and perform task with it

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> def echo(m):
...     print('got', repr(m))
>>> t.put('hey!')
1
>>> t.put('you!')
2
>>> t.perform(echo)
got 'hey!'
True
>>> t.perform(echo)
got 'you!'
True
>>> t.perform(echo)
False
poll(newest=None)

peek at the next message and increment internal pointer

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> t.put('hey!')
1
>>> t.put('you!')
2
>>> t.newest
-1
>>> t.poll()
'hey!'
>>> t.newest
1
>>> t.poll()
'you!'
>>> raised = False
>>> try:
...     t.poll()
... except EmptyException:
...     raised = True
>>> raised
True
>>> t.newest = -1
>>> t.poll()
'hey!'
pop()

read next message and remove it from the topic

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> t.put('hey!')
1
>>> t.put('you!')
2
>>> t.len()
2
>>> t._peek()
(1, 'test_topic', 'hey!')
>>> t.pop()
'hey!'
>>> t.len()
1
>>> t.pop()
'you!'
>>> t.len()
0
>>> t.pop()
>>> t.newest = -1
>>> raised = False
>>> try:
...     t._pop()
... except EmptyException:
...     raised = True
>>> raised
True
process(f)

respond to and consume current messages

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> def echo(m):
...     if m == 'quit': raise StopProcessing
...     print('got', repr(m))
>>> t.put('hey!')
1
>>> t.put('you!')
2
>>> t.put('quit')
3
>>> t.process(echo)
got 'hey!'
got 'you!'
2
>>> t.process(echo)
0
put(message)

put a message in the topic

send(*messages)

send list of messages

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> t.send('hey!', 'you!')
[1, 2]
>>> t.peek()
'hey!'
>>> t.peek()
'hey!'
wait(delay=0.1, timeout=15)

wait for a message to arrive and return it

>>> messages = setup_test()
>>> t = messages.get('test_topic')
>>> t.put('hey!')
1
>>> t.put('you!')
2
>>> t.wait()
'hey!'
>>> t.wait()
'you!'
exception zoom.queues.EmptyException

Bases: Exception

exception zoom.queues.WaitException

Bases: Exception

exception zoom.queues.StopListening

Bases: Exception

exception zoom.queues.StopHandling

Bases: Exception

exception zoom.queues.StopProcessing

Bases: Exception

zoom.records module

zoom.records

record store

class zoom.records.RecordStore(db, record_class=<class 'dict'>, name=None, key='id')

Bases: zoom.store.Store

stores records

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> people.kind
'person'
>>> joe = Person(name='Joe', age=20, birthdate=datetime.date(1992,5,5))
>>> repr(joe) == (
...     "<Person {'name': 'Joe', 'age': 20, "
...     "'birthdate': datetime.date(1992, 5, 5)}>"
... )
True
>>> people.put(joe)
1
>>> person = people.get(1)
>>> repr(person) == (
...     "<Person {'name': 'Joe', 'age': 20, "
...     "'birthdate': datetime.date(1992, 5, 5)}>"
... )
True
>>> sally = Person(name='Sally', kids=0,
...             birthdate=datetime.date(1992,5,5))
>>> people.put(sally)
2
>>> sally = people.find(name='Sally')
>>> repr(sally) == (
...     "[<Person {'name': 'Sally', "
...     "'kids': 0, 'birthdate': datetime.date(1992, 5, 5)}>]"
... )
True
>>> sally = people.first(name='Sally')
>>> repr(sally) == (
...     "<Person {'name': 'Sally', "
...     "'kids': 0, 'birthdate': datetime.date(1992, 5, 5)}>"
... )
True
>>> sally.kids += 1
>>> people.put(sally)
2
>>> repr(people.first(name='Sally')) == (
...     "<Person {'name': 'Sally', "
...     "'kids': 1, 'birthdate': datetime.date(1992, 5, 5)}>"
... )
True
>>> sally = people.first(name='Sally')
>>> sally.kids += 1
>>> people.put(sally)
2
>>> repr(people.first(name='Sally')) == (
...     "<Person {'name': 'Sally', "
...     "'kids': 2, 'birthdate': datetime.date(1992, 5, 5)}>"
... )
True
>>> sally = people.first(name='Sally')
>>> sally.kids += 1
>>> people.put(sally)
2
>>> repr(people.first(name='Sally')) == (
...     "<Person {'name': 'Sally', "
...     "'kids': 3, 'birthdate': datetime.date(1992, 5, 5)}>"
... )
True
>>> class Account(Record): pass
>>> class Accounts(RecordStore): pass
>>> accounts = Accounts(db, Account, key='account_id')
>>> accounts.kind
'account'
>>> account = Account(name='Joe', added=datetime.date(1992,5,5))
>>> repr(account) == (
...     "<Account {'name': 'Joe', 'added': datetime.date(1992, 5, 5)}>"
... )
True
>>> id = accounts.put(account)
>>> id
1
>>> accounts.put(Account(name='Sam', added=datetime.date(2001,1,1)))
2
>>> accounts.put(Account(name='Sal', added=datetime.date(2001,1,1)))
3
>>> account = accounts.get(1)
>>> print(accounts)
account
account_id name added
---------- ---- ----------
         1 Joe  1992-05-05
         2 Sam  2001-01-01
         3 Sal  2001-01-01
3 account records
>>> print(accounts.first(name='Sam'))
Account
  account_id ..........: 2
  name ................: 'Sam'
  added ...............: datetime.date(2001, 1, 1)
>>> print(accounts.find(added=datetime.date(2001, 1, 1)))
account
account_id name added
---------- ---- ----------
         2 Sam  2001-01-01
         3 Sal  2001-01-01
2 account records
>>> accounts.delete(2)
[2]
>>> print(accounts)
account
account_id name added
---------- ---- ----------
         1 Joe  1992-05-05
         3 Sal  2001-01-01
2 account records
all()

Retrieves all entities

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sally', age=25))
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Joe', age=25))
>>> repr(people.all()) == (
...     "[<Person {'name': 'Sally', 'age': 25}>, "
...     "<Person {'name': 'Sam', 'age': 25}>, "
...     "<Person {'name': 'Joe', 'age': 25}>]"
... )
True
delete(*args, **kwargs)

delete a record

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sally', age=25))
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Joe', age=25))
>>> joe = people.get(id)
>>> id
3
>>> bool(joe)
True
>>> joe
<Person {'name': 'Joe', 'age': 25}>
>>> people.delete(id)
[3]
>>> joe = people.get(id)
>>> joe
>>> bool(joe)
False
>>> bool(people.find(name='Sally'))
True
>>> people.delete(name='Sallie')
>>> bool(people.find(name='Sally'))
True
>>> people.delete()
>>> people.delete(name='Sally')
[1]
>>> bool(people.find(name='Sally'))
False
>>> db.close()
exists(keys=None)

tests for existence of a record

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sally', age=25))
>>> id
1
>>> sally = people.get(id)
>>> sally
<Person {'name': 'Sally', 'age': 25}>
>>> people.exists(1)
True
>>> people.exists(2)
False
>>> people.exists([1, 2])
[True, False]
>>> id = people.put(Person(name='Sam', age=25))
>>> people.exists([1, 2])
[True, True]
filter(function)

finds records that satisfiy filter

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sam Adam Jones', age=25))
>>> id = people.put(Person(name='Sally Mary Smith', age=55))
>>> id = people.put(Person(name='Bob Marvin Smith', age=25))
>>> list(people.filter(lambda a: 'Mary' in a.name))
[<Person {'name': 'Sally Mary Smith', 'age': 55}>]
>>> repr(list(people.filter(lambda a: a.age < 40))) == (
...     "[<Person {'name': 'Sam Adam Jones', 'age': 25}>, "
...     "<Person {'name': 'Bob Marvin Smith', 'age': 25}>]"
... )
True
find(**kwargs)

finds entities that meet search criteria

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Sally', age=55))
>>> id = people.put(Person(name='Bob', age=25))
>>> print(people.find(age=25))
person
_id name age
--- ---- ---
  1 Sam   25
  3 Bob   25
2 person records
>>> people.find(name='Sam')
[<Person {'name': 'Sam', 'age': 25}>]
>>> len(people.find(name='Sam'))
1
first(**kwargs)

finds the first record that meet search criteria

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Sally', age=55))
>>> id = people.put(Person(name='Bob', age=25))
>>> people.first(age=5)
>>> people.first(age=25)
<Person {'name': 'Sam', 'age': 25}>
get(keys)

retrives records

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(**{'name': 'Sam', 'age':15}))
>>> sam = people.get(id)
>>> sam
<Person {'name': 'Sam', 'age': 15}>
>>> people.put(Person(name='Jim',age=21))
2
>>> print(people)
person
_id name age
--- ---- ---
  1 Sam   15
  2 Jim   21
2 person records
>>> people.put(Person(name='Alice',age=29))
3
>>> print(people.get([1, 3]))
person
_id name  age
--- ----- ---
  1 Sam    15
  3 Alice  29
2 person records
get_attributes()

get complete set of attributes for the record type

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> sam = Person(**{'name': 'Sam', 'age':15})
>>> sorted(sam.keys())
['age', 'name']
>>> id = people.put(sam)
>>> people.get_attributes()
['name', 'age', 'kids', 'birthdate']
id_name
last(**kwargs)

finds the last record that meet search criteria

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Sally', age=55))
>>> id = people.put(Person(name='Bob', age=25))
>>> people.last(age=5)
>>> people.last(age=25)
<Person {'name': 'Bob', 'age': 25}>
put(record)

stores a record

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> sally = Person(name='Sally', age=25)
>>> sally
<Person {'name': 'Sally', 'age': 25}>
>>> id = people.put(Person(name='Sally', age=25))
>>> id
1
>>> sally
<Person {'name': 'Sally', 'age': 25}>
>>> sally = people.get(id)
>>> sally
<Person {'name': 'Sally', 'age': 25}>
>>> sally.age = 35
>>> people.put(sally)
1
>>> person = people.get(id)
>>> person
<Person {'name': 'Sally', 'age': 35}>
>>> id = people.put({'name':'James', 'age':15})
>>> id
2
>>> people.get(id)
<Person {'name': 'James', 'age': 15}>
search(text)

search for records that match text

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sam Adam Jones', age=25))
>>> id = people.put(Person(name='Sally Mary Smith', age=55))
>>> id = people.put(Person(name='Bob Marvin Smith', age=25))
>>> repr(list(people.search('smi'))) == (
...     "[<Person {'name': 'Sally Mary Smith', 'age': 55}>, "
...     "<Person {'name': 'Bob Marvin Smith', 'age': 25}>]"
... )
True
>>> list(people.search('bo smi'))
[<Person {'name': 'Bob Marvin Smith', 'age': 25}>]
>>> list(people.search('smi 55'))
[<Person {'name': 'Sally Mary Smith', 'age': 55}>]
zap()

deletes all entities of the given kind

>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sally', age=25))
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Joe', age=25))
>>> repr(people.all()) == (
...     "[<Person {'name': 'Sally', 'age': 25}>, "
...     "<Person {'name': 'Sam', 'age': 25}>, "
...     "<Person {'name': 'Joe', 'age': 25}>]"
... )
True
>>> people.zap()
>>> people.all()
[]
class zoom.records.Result(rows, storage)

Bases: object

rows resulting from a method call

zoom.records.get_result_iterator(rows, storage)

returns an iterator that iterates over the rows and zips the names onto the items being iterated so they come back as dicts

zoom.records.table_of(klass, db=None)

Return a table of Records of the given class

The klass parameter can be a subclass of zoom.Model or a table name. If a zoom.Model is provided the actual table name is derived from the class name. If the table name is provivded then it’s taken as-is.

Uses the current site database if none is provided.

>>> site = zoom.sites.Site()
>>> users = table_of('users', site.db)
>>> user = users.first(username='admin')
>>> user['first_name']
'Admin'

zoom.render module

zoom.render

rendering tools

zoom.render.add_helpers(*providers)

Add helpers to the helpers registry

zoom.render.apply_helpers(template, obj, providers)

employ helpers to fill in a template

>>> class User(object): pass
>>> user = User()
>>> user.name = 'World'
>>> apply_helpers('Hello {{name}}!', user, {})
'Hello World!'
>>> apply_helpers('Hello <dz:other>!', user, [{'other': 'Sam'}])
'Hello Sam!'
>>> apply_helpers('Hello <dz:other>!', user, {})
'Hello <dz:other>!'
>>> apply_helpers('Hello {{other}}!', user, {})
'Hello {{other}}!'
zoom.render.handler(request, handle, *rest)

Render handler

zoom.render.render(template, *providers, **helpers)

Render a template

Applies providers and helpers to the template to fill in the tags creating completed content.

>>> zoom.system.providers = []
>>> render('test')
'test'
>>> name = 'Sally'
>>> render('Hello {{name}}!', name=name)
'Hello Sally!'
>>> def name(): return 'Joe'
>>> render('Hello {{name}}!', dict(name=name))
'Hello Joe!'

zoom.request module

zoom.request

Web requsets.

class zoom.request.Request(env=None, instance=None, start_time=None, username=None)

Bases: object

A web request

>>> url = 'http://localhost/test?name=joe&age=40'
>>> request = build(url)
>>> request.body_consumed
False
>>> request.data == {'age': '40', 'name': 'joe'}
True
>>> request.path == '/test'
True
>>> request.route == ['test']
True
>>> request.helpers()['host']
'localhost'
>>> request.method
'GET'
>>> request.port
'80'
>>> request = build(url)
>>> request.body == sys.stdin
True
>>> request.body_consumed
True
>>> request.data == {}
True
body

access the body in raw form

data

access the body as data

elapsed

Elapsed time

helpers()

provide helpers

json_body

access and parse the body as json

parent_path

Path of resource parent

zoom.request.build(url, data=None, instance_path=None)

Build a request object

>>> request = build('http://localhost')
>>> request.path
''
>>> request.host
'localhost'
>>> request = build('http://testsite.local:8000/info')
>>> request.host
'testsite.local'
>>> request.port
8000
>>> request.path
'/info'
>>> request = build('http://localhost/hello')
>>> request.path
'/hello'
>>> build('https://localhost/hello?name=Sally').data
{'name': 'Sally'}
zoom.request.calc_domain(host)

calculate just the high level domain part of the host name

Remove the port and the www. if it exists.

>>> calc_domain('www.dsilabs.ca:8000')
'dsilabs.ca'
>>> calc_domain('test.dsilabs.ca:8000')
'test.dsilabs.ca'
zoom.request.get_instance(directory)

Figures out which instance to run

This function will first check to see if the instance directory passed contains a sites directory, the miniumum bar to be considered an instance directory. If so, it returns it’s absolute path.

If not, it will attempt to locate a Zoom configuration file which specifies the instance path.

If none of the above methods succeed it raises an exception.

zoom.request.get_library_instance()

get the location of the library instance

If the user doesn’t provide an instance whith which to run the server then the request assumes the user wants to run using the built-in instance. This is most common in development environments.

zoom.request.get_parent_dir()

get the directory above the current directory

zoom.request.get_web_vars(env)

return web parameters as a dict

zoom.request.handler(request, handle, *rest)

request handler

zoom.request.make_request_id()

make a unique request id

zoom.request.strim(url)

trim off the trailing ‘/’ character if there is one

>>> strim('http://localhost/')
'http://localhost'

zoom.response module

zoom.response

Various common web responses.

Note: We have chosen to use a dict for the headers even though technically the HTTP spec allows for multiple values by the same name because the uses cases for this seem to be very obscure and the benefits of not duplicating header entries that the dict provides seem to outweigh supporting obscure and generally not recommend use cases. The only use case where this is more commonly used is in cookies, but we deal with that special case in the cookie module.

class zoom.response.BinaryResponse(content, max_age=86400)

Bases: zoom.response.Response

Generic binary response

use max_age=0 to avoid caching

>>> response = BinaryResponse(b'binary data')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: application/octet-stream\n'
...     b'Cache-Control: max-age=86400\n'
...     b'ETag: e1a49b59e\n'
...     b'Content-length: 11\n\n'
...     b'binary data'
... )
>>> response.render() == expected
True
class zoom.response.CSSResponse(content, max_age=86400)

Bases: zoom.response.Response

CSS response

>>> response = CSSResponse(b'mycss')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: text/css;charset=utf-8\n'
...     b'Cache-Control: max-age=86400\n'
...     b'ETag: 12a586855\n'
...     b'Content-length: 5\n\n'
...     b'mycss'
... )
>>> response.render() == expected
True
class zoom.response.FileResponse(filename, content=None)

Bases: zoom.response.Response

File download response

>>> response = FileResponse('file.txt', content=b'mydata')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: application/octet-stream\n'
...     b'Content-Disposition: attachment; filename="file.txt"\n'
...     b'Cache-Control: no-cache\n'
...     b'Content-length: 6\n\n'
...     b'mydata'
... )
>>> response.render() == expected
True
class zoom.response.GIFResponse(content)

Bases: zoom.response.Response

GIF image response

>>> response = GIFResponse(b'myimage')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: image/gif\n'
...     b'Content-length: 7\n\n'
...     b'myimage'
... )
>>> response.render() == expected
True
class zoom.response.HTMLResponse(content='', status='200 OK')

Bases: zoom.response.TextResponse

HTML response

>>> HTMLResponse('test123').render() == (
...     b'Status: 200 OK\n'
...     b'Content-type: text/html\n'
...     b'Cache-Control: no-cache\n'
...     b'X-FRAME-OPTIONS: DENY\n'
...     b'Content-length: 7\n\n'
...     b'test123'
... )
True
>>> HTMLResponse('test123').as_wsgi() == (
...    '200 OK',
...    [
...       ('Content-type', 'text/html'),
...       ('Cache-Control', 'no-cache'),
...       ('X-FRAME-OPTIONS', 'DENY'),
...       ('Content-length', '7')
...    ],
...    b'test123'
... )
True
class zoom.response.ICOResponse(content, max_age=86400)

Bases: zoom.response.Response

ICO image response

>>> response = ICOResponse(b'myicon')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: image/x-icon\n'
...     b'Cache-Control: max-age=86400\n'
...     b'ETag: 78d2485ff\n'
...     b'Content-length: 6\n\n'
...     b'myicon'
... )
>>> response.render() == expected
True
render_doc()

Renders the payload

class zoom.response.JPGResponse(content)

Bases: zoom.response.Response

JPG image response

>>> response = JPGResponse(b'myimage')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: image/jpeg\n'
...     b'Content-length: 7\n\n'
...     b'myimage'
... )
>>> response.render() == expected
True
class zoom.response.JSONResponse(content, indent=4, sort_keys=True, ensure_ascii=False, **kwargs)

Bases: zoom.response.TextResponse

JSON response

>>> response = JavascriptResponse(b'myjson')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: application/javascript\n'
...     b'Cache-Control: max-age=86400\n'
...     b'ETag: f97258d47\n'
...     b'Content-length: 6\n\n'
...     b'myjson'
... )
>>> response.render() == expected
True
class zoom.response.JavascriptResponse(content, max_age=86400)

Bases: zoom.response.Response

Javascript response

>>> response = JavascriptResponse(b'myjs')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: application/javascript\n'
...     b'Cache-Control: max-age=86400\n'
...     b'ETag: 8be4a11f3\n'
...     b'Content-length: 4\n\n'
...     b'myjs'
... )
>>> response.render() == expected
True
class zoom.response.PDFResponse(filename, content=None)

Bases: zoom.response.FileResponse

PDF file download response

>>> response = PDFResponse('file.pdf', content=b'mydata')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: application/pdf\n'
...     b'Cache-Control: no-cache\n'
...     b'Content-length: 6\n\n'
...     b'mydata'
... )
>>> response.render() == expected
True
class zoom.response.PNGResponse(content, max_age=86400)

Bases: zoom.response.Response

PNG image response

>>> response = PNGResponse(b'myimage')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: image/png\n'
...     b'Cache-Control: max-age=86400\n'
...     b'ETag: b1a9acaf2\n'
...     b'Content-length: 7\n\n'
...     b'myimage'
... )
>>> response.render() == expected
True
class zoom.response.RedirectResponse(url)

Bases: zoom.response.TextResponse

Redirect response

>>> response = RedirectResponse('/')
>>> response.as_wsgi()
('302 Found', [('Location', '/'), ('Content-length', '0')], b'')
class zoom.response.Response(content=b'', status='200 OK', headers=None)

Bases: object

web response

>>> response = Response(b'this is it')
>>> response.render()
b'Status: 200 OK\nContent-length: 10\n\nthis is it'
>>> response.as_wsgi()
('200 OK', [('Content-length', '10')], b'this is it')
as_wsgi()

Render the entire response

render()

Renders the entire response

render_doc()

Renders the payload

class zoom.response.SiteNotFoundResponse(request)

Bases: zoom.response.HTMLResponse

Site 404 Not Found response

>>> request = zoom.utils.Bunch(
...     protocol='http',
...     host='localhost',
...     path='/',
...     ip_address='127.0.0.1',
...     module='index',
...     request_id=1234,
... )
>>> response = SiteNotFoundResponse(request)
>>> 'ZOOM' in str(response.render())
True
class zoom.response.TTFResponse(content, max_age=86400)

Bases: zoom.response.Response

True Type Font response

>>> response = TTFResponse(b'myfont')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: application/font-sfnt\n'
...     b'Cache-Control: max-age=86400\n'
...     b'ETag: 794c8f9c8\n'
...     b'Content-length: 6\n\n'
...     b'myfont'
... )
>>> response.render() == expected
True
class zoom.response.TextResponse(content='', status='200 OK')

Bases: zoom.response.Response

Plan text response

>>> response = TextResponse('mytext')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: text\n'
...     b'Cache-Control: no-cache\n'
...     b'Content-length: 6\n\n'
...     b'mytext'
... )
>>> response.render() == expected
True
render_doc()

Renders the payload

class zoom.response.WOFF2Response(content, max_age=86400)

Bases: zoom.response.Response

Web Open Font 2 Format response

>>> response = WOFF2Response(b'myfont')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: font/woff2\n'
...     b'Cache-Control: max-age=86400\n'
...     b'ETag: 794c8f9c8\n'
...     b'Content-length: 6\n\n'
...     b'myfont'
... )
>>> response.render() == expected
True
class zoom.response.WOFFResponse(content, max_age=86400)

Bases: zoom.response.Response

Web Open Font Format response

>>> response = WOFFResponse(b'myfont')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: application/font-woff\n'
...     b'Cache-Control: max-age=86400\n'
...     b'ETag: 794c8f9c8\n'
...     b'Content-length: 6\n\n'
...     b'myfont'
... )
>>> response.render() == expected
True
class zoom.response.XMLResponse(content='')

Bases: zoom.response.Response

XML response

>>> response = XMLResponse('myxml')
>>> expected = (
...     b'Status: 200 OK\n'
...     b'Content-type: text/xml\n'
...     b'Cache-Control: no-cache\n'
...     b'Content-length: 26\n\n'
...     b'<?xml version="1.0"?>myxml'
... )
>>> response.render() == expected
True
render_doc()

Renders the payload

zoom.server module

zoom.server

runs an instance of Zoom using the builtin Python WSGI server.

>>> server = WSGIApplication()
class zoom.server.WSGIApplication(instance='.', handlers=None, username=None)

Bases: object

a WSGI Application wrapper

class zoom.server.ZoomWSGIRequestHandler(request, client_address, server)

Bases: wsgiref.simple_server.WSGIRequestHandler

log_message(fmt, *args)

Log an arbitrary message.

This is used by all other logging functions. Override it if you have specific logging wishes.

The first argument, FORMAT, is a format string for the message to be logged. If the format string contains any % escapes requiring parameters, they should be specified as subsequent arguments (it’s just like printf!).

The client ip and current date/time are prefixed to every message.

logger = <Logger zoom.server (WARNING)>
zoom.server.application(environ, start_response)

run Zoom using external WSGI Server

Assumes that the WSGI script is located one directory below the instance directory. In an typical installation the instance directory would be /work/web and the WSGI script would be located in /work/web/www.

If you need to launch from somewhere else just build a function like this of your own and create the WSGIApplication instance using a path of your choosing.

>>> save_dir = os.getcwd()
>>> try:
...     env = dict(DOCUMENT_ROOT=zoom.tools.zoompath('web', 'www'))
...     response = application(env, lambda a, b: None)
... finally:
...     os.chdir(save_dir)
>>> len(response)
1
zoom.server.debug(environ, start_response)

Configuration Debugging App

zoom.server.reset_modules()

reset the modules to a known starting set

memorizes the modules currently in use and then removes any other modules when called again

zoom.server.run(port=80, instance=None, handlers=None, username=None)

run using internal HTTP Server

The instance variable is the path of the directory on the system where the sites folder is located. (e.g. /work/web)

zoom.server.run_as_cgi(environ=None, instance=None)

Run Zoom as a CGI script

zoom.services module

zoom.services

background services

zoom.services.run(command, returncode=False, location=None)

Run a shell command and return the response as a string

>>> run("echo testing")
'testing\n'
>>> run("echo testing", location="/")
'testing\n'

zoom.session module

zoom.session

class zoom.session.Session(request)

Bases: object

destroy()
load(db, token)

load a session

new(db, timeout=60)

create a new session

save(db, timeout=60)

save a session

class zoom.session.Sessions

Bases: zoom.utils.Record

zoom.session.handler(request, handler, *rest)

zoom.settings module

zoom.settings

Classes for mananging settings.

class zoom.settings.AppSettings

Bases: zoom.utils.Record

settings storage class

class zoom.settings.Settings

Bases: object

Settings storage class

clear()

Clear all settings

load()

get the stash value

save(value)

put the stash value

class zoom.settings.SettingsController

Bases: zoom.mvc.Controller

settings controller

clear()

clear settings

form = None
get_fields()

Get the settings fields

Override this method to provide settings fields specific to your app.

get_form()

Get the settings form

Override this method to provide a settings form specific to your app.

index(**kwargs)

show the settings form

save_button(**values)

save settings

class zoom.settings.SettingsSection

Bases: zoom.utils.Record

settings section

class zoom.settings.SiteSettings(config)

Bases: object

Site Settings

clear()

Clear all settings

get(section, name, default=None)
items(section)
load()

load the settings values

save()

save the settings values

section(name)
update(section, values)
values
class zoom.settings.SystemSettings

Bases: zoom.utils.Record

site settings

zoom.site module

zoom.site

class zoom.site.ConfigSection

Bases: zoom.utils.Record

site configuration section

class zoom.site.Site(request)

Bases: object

a Zoom site

abs_url

Calculate an absolute URL for this site

apps

Return list of apps installed on this site

Returns a link for the site owner.

helpers()

provide helpers

settings
tracker

Returns a Google analytics tracker code snippet

class zoom.site.SiteConfig(config)

Bases: object

Site Config Reader

Site configuration is managed with the site.ini files provided in the site directory and the default directory.

The SiteConfig class maps unique config keywords used by the system into the section/name pairs used in the physical configuration files and provides default values in the event that no value is provided in the config files.

>>> site = zoom.sites.Site()
>>> site.config.get('site', 'name') # without SiteConfig
'ZOOM'
>>> conf = SiteConfig(site.config) # using SiteConfig
>>> conf.site.get('name')
'ZOOM'
>>> conf.section('sessions')
<ConfigSection {'secure_cookies': True}>
>>> conf.section('notasection')
<ConfigSection {}>
>>> conf.site['name']
'ZOOM'
>>> conf.site.get('notaname', 'Nope')
'Nope'
>>> conf.site.notaname == None
True
>>> conf.mail.get('smtp_port')
'587'
>>> conf.mail.smtp_port
'587'
defaults = {'apps': {'index': 'content', 'home': 'home', 'login': 'login', 'path': 'apps;../../apps', 'include_basics': True}, 'error': {'users': False}, 'mail': {'smtp_host': '', 'smtp_port': '587', 'smtp_user': '', 'smtp_passwd': '', 'logo': '', 'from_addr': '', 'from_name': 'ZOOM Support', 'gnupg_home': None}, 'monitoring': {'profiling': False, 'logging': False, 'app_database': False, 'system_database': False}, 'sessions': {'secure_cookies': True}, 'site': {'name': 'ZOOM', 'url': '', 'owner_name': 'Company Name', 'owner_email': '', 'owner_url': 'https://www.dynamic-solutions.com', 'admin_email': '', 'register_email': '', 'support_email': ''}, 'theme': {'name': 'default', 'path': None}, 'users': {'default': 'guest', 'administrators_group': 'administrators', 'developers_group': 'developers', 'override': None}}
items(section)
section(name)
zoom.site.handler(request, next_handler, *rest)

install site object

zoom.sites module

zoom.sites

Updated Zoom sites module. Eventually, the functionality currently in zoom.site will make it’s way here.

Note: experimental

class zoom.sites.BackgroundJob(name, path)

Bases: object

Background Job

class zoom.sites.Site(path=None)

Bases: zoom.site.Site

a Zoom site

A zoom site can be completely determined by the path to the site directory. That directory may contain a config file, apps, and other assets. If any assets are required that are not found in the site directory, the system will look in the default site as a fallback. Any site setttings not found in the site config file will be obtained from the default site. Any settings not found there will rely on built-in defaults.

This Site object is not to be confused with the zoom.site.Site object, renamed here as BasicSite, which will eventually be phased out as that functionality is brought into this module.

>>> site = Site()
>>> site.name
'localhost'
>>> 'users' in site.db.get_tables()
True
get_background_jobs()

Returns a dict of background jobs

>>> site = Site()
>>> site.name
'localhost'
>>> site.get_background_jobs()
[BackgroundJob('hello')]
class zoom.sites.SiteProxy(path)

Bases: object

Site proxy

run_background_jobs()

Run background jobs

Iterates through the apps in the site and calls run_background_jobs on each one.

>>> import zoom
>>> site_directory = zoom.tools.zoompath('web/sites/localhost')
>>> site = SiteProxy(site_directory)
>>> site.run_background_jobs()
localhost

zoom.snippets module

zoom.snippets

zoom.snippets.Snippet

alias of zoom.snippets.SystemSnippet

class zoom.snippets.SystemSnippet

Bases: zoom.utils.Record

A chunk of text (usually HTML) that can be rendered by placing the {{snippet}} tag in a document or template.

>>> db = zoom.database.setup_test()
>>> snippets = get_snippets(db)
>>> snippets.delete(name='test')
>>> snippets.find(name='test')
[]
>>> t = snippets.put(Snippet(name='test', body='some text'))
>>> snippets.find(name='test')
[<SystemSnippet {'key': 'test', 'name': 'test', 'url': '/content/snippets/test', 'body': 'some text', 'link': '<a href="/content/snippets/test">test</a>'}>]
allows(user, action)

Item level policy

key

Return a link

url
zoom.snippets.get_snippets(db=None)
zoom.snippets.snippet(name, default='', variant=None)

zoom.sqltools module

zoom.sql

sql utilities

zoom.sqltools.setup_test()

setup test

zoom.sqltools.summarize(table, dimensions, metrics=None)

summarize data

>>> from zoom.records import Record, RecordStore
>>> from decimal import Decimal
>>> db = setup_test()
>>> class Person(Record): pass
>>> class People(RecordStore): pass
>>> people = People(db, Person)
>>> put = people.put
>>> id = put(Person(name='Sam', age=25, kids=1, salary=Decimal('40000')))
>>> id = put(Person(name='Sally', age=55, kids=4, salary=Decimal('80000')))
>>> id = put(Person(name='Bob', age=25, kids=2, salary=Decimal('70000')))
>>> id = put(Person(name='Jane', age=25, kids=2, salary=Decimal('50000')))
>>> id = put(Person(name='Alex', age=25, kids=3, salary=Decimal('50000')))
>>> print(people)
person
_id name  age kids salary
--- ----- --- ---- ------
  1 Sam    25    1 40,000
  2 Sally  55    4 80,000
  3 Bob    25    2 70,000
  4 Jane   25    2 50,000
  5 Alex   25    3 50,000
5 person records
>>> print(summarize('person', ['age']))
select "*" age, count(*) n from person group by 1
union select age age, count(*) n from person group by 1
>>> print(db(summarize('person', ['age'])))
age n
--- -
25  4
55  1
*   5
>>> print(db(summarize('person', ['age','kids'])))
age kids n
--- ---- -
25  1    1
25  2    2
25  3    1
25  *    4
55  4    1
55  *    1
*   1    1
*   2    2
*   3    1
*   4    1
*   *    5
>>> print(db(summarize('person', ['age','kids'], ['salary'])))
age kids n salary
--- ---- - -------
25  1    1  40,000
25  2    2 120,000
25  3    1  50,000
25  *    4 210,000
55  4    1  80,000
55  *    1  80,000
*   1    1  40,000
*   2    2 120,000
*   3    1  50,000
*   4    1  80,000
*   *    5 290,000
>>> people.zap()
>>> print(people)
Empty list

zoom.store module

zoom.store

key value store

class zoom.store.EntityStore(db, klass=<class 'dict'>, kind=None)

Bases: zoom.store.Store

stores entities

>>> db = setup_test()
>>> stuff = EntityStore(db)
>>> stuff.put(dict(name='Joe', age=14))
1
>>> stuff.put(dict(name='Sally', age=34))
2
>>> stuff.put(dict(name='Sam', age=34))
3
>>> print(zoom.utils.RecordList(stuff.find(name='Joe')))
dict
_id name age
--- ---- ---
  1 Joe   14
1 dict records
>>> s = stuff.find(age=34)
>>> print(s)
dict
_id name  age
--- ----- ---
  2 Sally  34
  3 Sam    34
2 dict records
>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> people.kind
'person'
>>> joe = Person(name='Joe', age=20, birthdate=datetime.date(1992,5,5))
>>> joe
<Person {'name': 'Joe', 'age': 20, 'birthdate': datetime.date(1992, 5, 5)}>
>>> people.put(joe)
1
>>> person = people.get(1)
>>> person
<Person {'name': 'Joe', 'age': 20, 'birthdate': datetime.date(1992, 5, 5)}>
>>> sally = Person(name='Sally', kids=0, birthdate=datetime.date(1992,5,5))
>>> people.put(sally)
2
>>> sally = people.find(name='Sally')
>>> sally
[<Person {'name': 'Sally', 'kids': 0, 'birthdate': datetime.date(1992, 5, 5)}>]
>>> sally = people.first(name='Sally')
>>> sally
<Person {'name': 'Sally', 'kids': 0, 'birthdate': datetime.date(1992, 5, 5)}>
>>> sally.kids += 1
>>> people.put(sally)
2
>>> people.first(name='Sally')
<Person {'name': 'Sally', 'kids': 1, 'birthdate': datetime.date(1992, 5, 5)}>
>>> sally = people.first(name='Sally')
>>> sally.kids += 1
>>> people.put(sally)
2
>>> people.first(name='Sally')
<Person {'name': 'Sally', 'kids': 2, 'birthdate': datetime.date(1992, 5, 5)}>
>>> sally = people.first(name='Sally')
>>> sally.kids += 1
>>> people.put(sally)
2
>>> people.first(name='Sally')
<Person {'name': 'Sally', 'kids': 3, 'birthdate': datetime.date(1992, 5, 5)}>
>>> class Misc(EntityStore): pass
>>> misc = Misc(db, dict)
>>> config_info = dict(host='database', name='somename')
>>> id = misc.put(config_info)
>>> x = misc.put(dict(other='this', stuff='that'))
>>> my_info = misc.get(id)
>>> Record(my_info)
<Record {'name': 'somename', 'host': 'database'}>
>>> Record(misc.get(x))
<Record {'other': 'this', 'stuff': 'that'}>
>>> people = EntityStore(db, 'person')
>>> people.klass
<class 'dict'>
>>> people.kind
'person'
>>> print(sorted(people.first(name='Sally').items()))
[('__store', <EntityStore(dict)>), ('_id', 2), ('birthdate', datetime.date(1992, 5, 5)), ('kids', 3), ('name', 'Sally')]
>>> print(Person(people.first(name='Sally')))
Person
  name ................: 'Sally'
  kids ................: 3
  birthdate ...........: datetime.date(1992, 5, 5)
>>> EntityStore(db, 'person').first(name='Joe')['age']
20
>>>
>>> name = 'somename'
>>> id = misc.put(dict(host='database', name=name))
>>> my_info = misc.get(id)
>>> assert type(my_info['name'])==type(name)
all()

Retrieves all entities

>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sally', age=25))
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Joe', age=25))
>>> people.all()
[<Person {'name': 'Sally', 'age': 25}>, <Person {'name': 'Sam', 'age': 25}>, <Person {'name': 'Joe', 'age': 25}>]
>>> db.close()
delete(*args, **kwargs)

delete entities

>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sally', age=25))
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Joe', age=25))
>>> joe = people.get(id)
>>> id
3
>>> bool(joe)
True
>>> joe
<Person {'name': 'Joe', 'age': 25}>
>>> people.delete(id)
[3]
>>> joe = people.get(id)
>>> joe
>>> bool(joe)
False
>>> bool(people.find(name='Sally'))
True
>>> people.delete(name='Sallie')
>>> bool(people.find(name='Sally'))
True
>>> people.delete()
>>> people.delete(name='Sally')
[1]
>>> bool(people.find(name='Sally'))
False
>>> db.close()
exists(keys=None)

tests for existence of an entity

>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sally', age=25))
>>> sally = people.get(id)
>>> sally
<Person {'name': 'Sally', 'age': 25}>
>>> people.exists(1)
True
>>> people.exists(2)
False
>>> people.exists([1, 2])
[True, False]
>>> id = people.put(Person(name='Sam', age=25))
>>> people.exists([1, 2])
[True, True]
>>> db.close()
find(**kv)

finds entities that meet search criteria

>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Sally', age=55))
>>> id = people.put(Person(name='Bob', age=25))
>>> print(people.find(age=25))
person
_id name age
--- ---- ---
  1 Sam   25
  3 Bob   25
2 person records
>>> len(people.find(name='Sam'))
1
>>> db.close()
first(**kv)

finds the first entity that meet search criteria

>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Sally', age=55))
>>> id = people.put(Person(name='Bob', age=25))
>>> people.first(age=5)
>>> people.first(age=55)
<Person {'name': 'Sally', 'age': 55}>
>>> people.first()
<Person {'name': 'Sam', 'age': 25}>
>>> db.close()
get(keys)

retrives entities

>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(**{'name': 'Sam', 'age':15,
...     'salary': decimal.Decimal('100.00')}))
>>> sam = people.get(id)
>>> sam
<Person {'name': 'Sam', 'age': 15, 'salary': Decimal('100.00')}>
>>> people.put(Person(name='Jim', age=21,
...    salary=decimal.Decimal('50')))
2
>>> people.put(Person(name='Alice', age=29))
3
>>> print(people)
person
_id name  age salary
--- ----- --- ------
  1 Sam    15 100.00
  2 Jim    21 50
  3 Alice  29 None
3 person records
>>> print(people.get([1, '3']))
person
_id name  age salary
--- ----- --- ------
  1 Sam    15 100.00
  3 Alice  29 None
2 person records
>>> db.close()
get_attributes()

get complete set of attributes for the entity type

>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> sam = Person(**{'name': 'Sam', 'age':15})
>>> sorted(sam.keys())
['age', 'name']
>>> id = people.put(sam)
>>> sorted(people.get_attributes())
['age', 'name']
>>> db.close()
last(**kv)

finds the last entity that meet search criteria

>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Sally', age=55))
>>> id = people.put(Person(name='Bob', age=25))
>>> people.last(age=5)
>>> people.last(age=25)
<Person {'name': 'Bob', 'age': 25}>
>>> db.close()
put(entity)

stores an entity

>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> sally = Person(name='Sally', age=25)
>>> sally
<Person {'name': 'Sally', 'age': 25}>
>>> id = people.put(Person(name='Sally', age=25))
>>> id
1
>>> sally
<Person {'name': 'Sally', 'age': 25}>
>>> sally = people.get(id)
>>> sally
<Person {'name': 'Sally', 'age': 25}>
>>> sally.age = 35
>>> people.put(sally)
1
>>> person = people.get(id)
>>> person
<Person {'name': 'Sally', 'age': 35}>
>>> id = people.put({'name':'James', 'age':15})
>>> id
2
>>> people.get(id)
<Person {'name': 'James', 'age': 15}>
>>> classes = ['one', 'Not this one']
>>> grades = (('one', 'A'), ('Not this one', 'C+'), )
>>> id = people.put({'name':'James', 'classes':classes, 'grades': grades})
>>> assert classes == people.get(id).classes
>>> assert len(people.get(id).grades) == 2  # json dump/load will bring back all tuples as lists
>>> db.close()
search(text)

search for entities that match text

>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Sally', age=55))
>>> id = people.put(Person(name='Bob', age=25))
>>> list(people.search('bob'))
[<Person {'name': 'Bob', 'age': 25}>]
>>> for r in list(people.search(25)): print(r)
Person
  name ................: 'Sam'
  age .................: 25
Person
  name ................: 'Bob'
  age .................: 25
>>> list(people.search('Bill'))
[]
>>> db.close()
zap()

deletes all entities of the given kind

>>> db = setup_test()
>>> class Person(Entity): pass
>>> class People(EntityStore): pass
>>> people = People(db, Person)
>>> id = people.put(Person(name='Sally', age=25))
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Joe', age=25))
>>> people.all()
[<Person {'name': 'Sally', 'age': 25}>, <Person {'name': 'Sam', 'age': 25}>, <Person {'name': 'Joe', 'age': 25}>]
>>> people.zap()
>>> people.all()
[]
>>> db.close()
class zoom.store.Store

Bases: object

after_delete(record)
after_insert(record)
after_update(record)
before_delete(record)
before_insert(record)
before_update(record)
zoom.store.entify(rs, storage)

converts query result into an EntityList

zoom.store.store_of(klass, db=None)

Returns a store of the given entity class

The klass parameter can be a subclass of zoom.Model or an entity kind. If a zoom.Model is provided the actual entity kind is derived from the class name. If the entity kind is provivded as a string then it’s taken as-is.

Uses the current site database if none is provided.

>>> site = zoom.sites.Site()
>>> class Person(Entity): pass
>>> people = store_of(Person, site.db)
>>> id = people.put(Person(name='Sam', age=25))
>>> id = people.put(Person(name='Sally', age=55))
>>> id = people.put(Person(name='Bob', age=25))
>>> person = people.first(name='Sally')
>>> person['age']
55

zoom.templates module

templates.zoom

zoom.tools module

zoom.tools

class zoom.tools.Redirector(*args, **kwargs)

Bases: object

render(request)

render redirect

zoom.tools.ensure_listy(obj)

ensure object is wrapped in a list if it can’t behave like one

>>> ensure_listy('not listy')
['not listy']
>>> ensure_listy(['already listy'])
['already listy']
>>> ensure_listy([])
[]
zoom.tools.first_day_of_last_month(any_date)

Returns the first day of last month for any date

>>> first_day_of_last_month(datetime.date(2016, 1, 21))
datetime.date(2015, 12, 1)
zoom.tools.first_day_of_next_month(any_date)

returns the first day of next month for any date

>>> first_day_of_next_month(datetime.date(2016, 2, 1))
datetime.date(2016, 3, 1)
zoom.tools.first_day_of_the_month(any_date)

returns the first day of the month for any date

>>> first_day_of_the_month(datetime.date(2016, 12, 31))
datetime.date(2016, 12, 1)
zoom.tools.get_markdown_converter()

Return a configured markdown converter

>>> markdown("a [[wikilink]] test")
'<p>a <a class="wikilink" href="wikilink.html">wikilink</a> test</p>'
>>> markdown("a [[wikilink.html]] test")
'<p>a [[wikilink.html]] test</p>'
zoom.tools.get_template(template_name='default', theme='default')

Get site page template

zoom.tools.hide_helpers(content)

prevent helper requests from being filled

zoom.tools.home(view=None)

Redirect to application home.

zoom.tools.how_long(time1, time2)

Returns a string that describes the difference between two times.

>>> import time
>>> now = now()
>>> how_long(now, now)
'a moment'
>>> how_long(now, now + one_minute / 3)
'20 seconds'
>>> how_long(now, now + one_hour / 3)
'20 minutes'
>>> how_long(now, now + one_day / 3)
'8 hours'
>>> how_long(now, now + one_day)
'1 day'
>>> how_long(now, now + 2 * one_day)
'2 days'
>>> how_long(now, now + 15 * one_day)
'2 weeks'
>>> how_long(now, now + 35 * one_day)
'over a month'
>>> how_long(now, now + 65 * one_day)
'over 2 months'
>>> how_long(now, now + 361 * one_day)
'almost a year'
>>> how_long(now, now + 20 * one_minute)
'20 minutes'
>>> how_long(now, now + 2 * 365 * one_day)
'almost two years'
>>> how_long(now, now + 3.25 * 365 * one_day)
'over 3 years'
>>> how_long(now, now + 1.25 * 365 * one_day)
'over a year'
>>> how_long(today(), tomorrow(today()))
'1 day'
>>> how_long(today(), now + one_week)
'7 days'
>>> how_long(now, time.time())
'a moment'
>>> failed = False
>>> try:
...    how_long(now, None)
... except TypeError:
...    failed = True
>>> failed
True
zoom.tools.how_long_ago(anytime, since=None)

Returns a string that describes the difference between any time and now.

>>> now = now()
>>> how_long_ago(now - datetime.timedelta(1) * 2)
'2 days ago'
>>> how_long_ago(now + 20 * one_minute)
'19 minutes from now'
>>> how_long_ago(now - 20 * one_minute)
'20 minutes ago'
>>> how_long_ago(now - 20 * one_minute, now - 10 * one_minute)
'10 minutes ago'
zoom.tools.htmlquote(text)

Encodes text for raw use in HTML.

>>> htmlquote(u"<'&\">")
'&lt;&#39;&amp;&quot;&gt;'
>>> htmlquote("<'&\">")
'&lt;&#39;&amp;&quot;&gt;'
zoom.tools.is_listy(obj)

test to see if an object will iterate like a list

>>> is_listy([1,2,3])
True
>>> is_listy(set([3,4,5]))
True
>>> is_listy((3,4,5))
True
>>> is_listy(dict(a=1, b=2))
False
>>> is_listy('123')
False
zoom.tools.last_day_of_last_month(any_date)

Returns the first day of last month for any date

>>> last_day_of_last_month(datetime.date(2016, 1, 21))
datetime.date(2015, 12, 31)
zoom.tools.last_day_of_next_month(any_date)

returns the last day of next month for any date

>>> last_day_of_next_month(datetime.date(2016, 2, 1))
datetime.date(2016, 3, 31)
zoom.tools.last_day_of_the_month(any_date)

returns the last day of the month for any date

>>> last_day_of_the_month(datetime.date(2016, 2, 1))
datetime.date(2016, 2, 29)
>>> last_day_of_the_month(datetime.datetime(2016, 2, 1, 1, 1, 1))
datetime.date(2016, 2, 29)
zoom.tools.last_month(any_date)

Returns date range for last month for any date

>>> last_month(datetime.date(2016, 1, 21))
(datetime.date(2015, 12, 1), datetime.date(2015, 12, 31))
zoom.tools.load(pathname, encoding='utf-8')

Read a file and return the contents

zoom.tools.load_content(pathname, *args, **kwargs)

Load a content file and use it to format parameters

zoom.tools.load_template(name, default=None)

Load a template from the theme folder.

Templates usually have .html file extensions and this module will assume that’s what is desired unless otherwise specified.

zoom.tools.markdown(content)

Transform content with markdown

>>> markdown('this **is** bold')
'<p>this <strong>is</strong> bold</p>'
zoom.tools.next_month(any_date)

Returns date range for next month for any date

>>> next_month(datetime.date(2016, 1, 21))
(datetime.date(2016, 2, 1), datetime.date(2016, 2, 29))
zoom.tools.now()

Return the current datetime

zoom.tools.redirect_to(*args, **kwargs)

Return a redirect response for a URL.

zoom.tools.restore_helpers(content)

Restores content helpers to their usual form

zoom.tools.this_month(any_date)

Returns date range for last month for any date

>>> this_month(datetime.date(2016, 1, 21))
(datetime.date(2016, 1, 1), datetime.date(2016, 1, 31))
zoom.tools.today()

Return the current date

>>> today() == datetime.date.today()
True
zoom.tools.tomorrow(any_date=None)

Return date for tomorrow

>>> tomorrow(datetime.date(2017, 12, 3))
datetime.date(2017, 12, 4)
>>> tomorrow(datetime.date(2016, 12, 31))
datetime.date(2017, 1, 1)
zoom.tools.unisafe(val)

safely convert to unicode

>>> unisafe(None)
''
>>> unisafe(b'123')
'123'
>>> unisafe(
...     b'\xe3\x81\x93\xe3\x82\x93\xe3\x81\xab\xe3\x81'
...     b'\xa1\xe3\x81\xaf\xe4\xb8\x96\xe7\x95\x8c'
... )
'こんにちは世界'
>>> unisafe(1)
'1'
zoom.tools.websafe(content)

Return htmlquoted version of content

>>> websafe(b'This could be <problematic>')
'This could be &lt;problematic&gt;'
zoom.tools.yesterday(any_date=None)

Return date for yesterday

>>> yesterday(datetime.date(2017, 12, 4))
datetime.date(2017, 12, 3)
>>> yesterday(datetime.date(2017, 1, 1))
datetime.date(2016, 12, 31)
zoom.tools.zoompath(*args)

Returns the location of a standard Zoom asset

zoom.users module

zoom.users

class zoom.users.User(*args, **kwargs)

Bases: zoom.utils.Record

Zoom User

activate()

Activate the user

add_group(group)

Make user a member of the group

allows(user, action)
apps

Returns the names of the apps the user can access

authenticate(password)

authenticate user credentials

authorize(action, thing)

authorize a user to perform an action on thing

If user is not allowed to perform the action an exception is raised. Object thing must provide allows(user, action) method.

can(action, thing)

test to see if user can action a thing object.

Object thing must provide allows(user, action) method.

can_run(app)

test if user can run an app

deactivate()

Deactivate the user

default_app

returns the default app for the user

full_name

user full name

get_groups()

get groups this user belongs to

>>> from zoom.database import setup_test
>>> users = Users(setup_test())
>>> user = users.first(username='guest')
>>> user.get_groups()[-4:]
['a_passreset', 'a_signup', 'everyone', 'guests']
>>> user = users.first(username='admin')
>>> user.get_groups()[:2]
['a_admin', 'a_apps']
get_memberships()
groups

Returns the groups the user belongs to

groups_ids

Returns the IDs for the groups the user belongs to

helpers()

provide user helpers

initialize(request)

Initialize user based on a request

is_active

get user active status

is_member(group)

determine if user is a member of a group

key

user as link

login(request, password, remember_me=False)

log user in

logout()

log user out

memberships
name

user full name

remove_groups()

Remove user membership in the group

set_password(password)

set the user password

status_text

Return status as human friendly text

update_last_seen()

Record the latest activity time for the user

avoid the record store put so as not to update the updated timestamp

Human friendly user account

url

user view url

user_id

Return user record id

when_last_seen
when_updated

Human friendly updated timestamp

class zoom.users.Users(db, entity=<class 'zoom.users.User'>)

Bases: zoom.records.RecordStore

Zoom Users

>>> import datetime
>>> from zoom.database import setup_test
>>> db = setup_test()
>>> users = Users(db)
>>> user = users.first(username='guest')
>>> user.created = datetime.datetime(2017, 3, 30, 17, 23, 43)
>>> user.updated = datetime.datetime(2017, 3, 30, 17, 23, 43)
>>> user.now = datetime.datetime(2017, 4, 30, 17, 23, 43)
>>> print(user)
User
  user_id .............: 3
  key .................: 'guest'
  name ................: 'Guest User'
  first_name ..........: 'Guest'
  last_name ...........: 'User'
  now .................: datetime.datetime(2017, 4, 30, 17, 23, 43)
  url .................: '/admin/users/guest'
  apps ................: ['content', 'forgot', 'login', 'passreset', 'signup']
  link ................: 'guest'
  email ...............: 'guest@datazoomer.com'
  phone ...............: ''
  groups ..............: ['everyone', 'guests']
  status ..............: 'A'
  created .............: datetime.datetime(2017, 3, 30, 17, 23, 43)
  request .............: None
  updated .............: datetime.datetime(2017, 3, 30, 17, 23, 43)
  is_admin ............: False
  password ............: ''
  username ............: 'guest'
  full_name ...........: 'Guest User'
  is_active ...........: True
  created_by ..........: 1
  groups_ids ..........: [4, 3]
  updated_by ..........: 1
  default_app .........: '/home'
  memberships .........: {3}
  status_text .........: 'active'
  is_developer ........: False
  when_updated ........: 'over a month ago'
  when_last_seen ......: 'never'
  updated_by_link .....: 'admin'
  is_authenticated ....: False
after_insert(user)

Things to do right after inserting a new user

before_delete(user)

Things to do right before deleting a user

before_insert(user)

Things to do just before inserting a new User record

before_update(user)

Things to do just before updating a User record

locate(key)
zoom.users.authorize(*roles)

Decorator that authorizes (or not) the current user

Raises an exception if the current user does not have at least one of the listed roles.

zoom.users.get_current_username(request)

get current user username

zoom.users.get_groups(db, user)

get groups for a user

>>> from zoom.database import setup_test
>>> db = setup_test()
>>> users = Users(db)
>>> guest = users.first(username='guest')
>>> guest.username
'guest'
>>> guest._id
3
>>> groups = get_groups(db, guest)
>>> len(groups)
7
>>> 'everyone' in groups
True
>>> 'a_login' in groups
True
>>> 'managers' in groups
False
>>> 'administrators' in groups
False
>>> admin = users.first(username='admin')
>>> groups = get_groups(db, admin)
>>> 'everyone' in groups
True
>>> 'a_login' in groups
False
>>> 'managers' in groups
True
>>> 'administrators' in groups
True
zoom.users.handler(request, next_handler, *rest)

handle user

zoom.users.key_for(username)

Calculates a valid HTML tag id given an arbitrary string.

>>> key_for('Test 123')
'test-123'
>>> key_for('New Record')
'new-record'
>>> key_for('New "special" Record')
'new-special-record'
>>> key_for("hi test")
'hi-test'
>>> key_for("hi-test")
'hi-test'
>>> key_for(1234)
'1234'
>>> key_for('this %$&#@^is##-$&*!it')
'this-is-it'
>>> key_for('test-this')
'test-this'
>>> key_for('test.this')
'test.this'
>>> key_for('test\\this')
'test-this'
zoom.users.set_current_user(request)

Set current user

Set the current user based on the current username.

zoom.utils module

zoom.utils

class zoom.utils.Bunch(**kwargs)

Bases: object

a handy bunch of variables

class zoom.utils.Config(filename)

Bases: object

Config File Reader

A Config with a handy get method.

>>> config = Config(zoom.tools.zoompath('web','sites','default','site.ini'))
>>> config.get('site', 'name')
'ZOOM'
>>> config.has_option('site', 'name')
True
>>> config.get('site', 'size', 100)
100
>>> try:
...     config.get('site', 'size')
... except (configparser.NoOptionError, configparser.NoSectionError):
...     error = True
>>> error
True
get(section, option, default=None)

Get a config file value supplying an optional defalt value.

has_option(section, option)

Return True if config file option exists.

has_section(section)

Return True if config file section exists.

class zoom.utils.DefaultRecord

Bases: zoom.utils.Record

A Record with default values

>>> class Foo(DefaultRecord): pass
>>> foo = Foo(name='Sam')
>>> foo.name
'Sam'
>>> foo.phone
''
class zoom.utils.ItemList(*args, **kwargs)

Bases: list

list of data items

>>> items = ItemList()
>>> items.append(['Joe', 12, 125])
>>> items
[['Joe', 12, 125]]
>>> print(items)
Column 0 Column 1 Column 2
-------- -------- --------
Joe            12      125
>>> items.insert(0, ['Name', 'Score', 'Points'])
>>> print(items)
Name Score Points
---- ----- ------
Joe     12    125
>>> data = [
...     ['Joe', 12, 125],
...     ['Sally', 13, 1354],
... ]
>>> items = ItemList(data)
>>> print(items)
Column 0 Column 1 Column 2
-------- -------- --------
Joe            12      125
Sally          13    1,354
>>> data = [
...     ['Joe', 12, 125],
...     ['Sally', 13, 135],
... ]
>>> items = ItemList(data, labels=['Name', 'Score', 'Points'])
>>> print(items)
Name  Score Points
----- ----- ------
Joe      12    125
Sally    13    135
class zoom.utils.OrderedSet(iterable=None)

Bases: collections.abc.MutableSet

A set that preserves the order of the elements

>>> s = OrderedSet('abracadaba')
>>> t = OrderedSet('simsalabim')
>>> print(s | t)
OrderedSet(['a', 'b', 'r', 'c', 'd', 's', 'i', 'm', 'l'])
>>> print(s & t)
OrderedSet(['a', 'b'])
>>> print(s - t)
OrderedSet(['r', 'c', 'd'])
>>> print(OrderedSet(reversed(s - t)))
OrderedSet(['d', 'c', 'r'])
>>> OrderedSet(['d', 'c', 'd']) == OrderedSet(['c', 'd', 'd'])
False

credit: http://code.activestate.com/recipes/576694/ Licensed under MIT License

add(key)

add an item

>>> s = OrderedSet([1, 2, 3])
>>> s.add(4)
>>> s
OrderedSet([1, 2, 3, 4])
discard(key)

discard an item by key

>>> s = OrderedSet([1, 2, 3])
>>> s.discard(1)
>>> s
OrderedSet([2, 3])
pop(last=True)

pop an item

>>> s = OrderedSet([1, 2, 3])
>>> s.pop(2)
3
>>> s
OrderedSet([1, 2])
class zoom.utils.Record

Bases: zoom.utils.Storage

A dict with attribute access to items, attributes and properties

>>> class Foo(Record):
...     full = property(lambda a: a.fname + ' ' + a.lname)
...
>>> f = Foo(fname='Joe', lname='Smith')
>>> f.full
'Joe Smith'
>>> f['full']
'Joe Smith'
>>> 'The name is %(full)s' % f
'The name is Joe Smith'
>>> print(f)
Foo
  fname ...............: 'Joe'
  lname ...............: 'Smith'
  full ................: 'Joe Smith'
>>> f.attributes()
['fname', 'lname', 'full']
>>> class FooBar(Record):
...     full = property(lambda a: a.fname + ' ' + a.lname)
...
>>> o = FooBar(a=2)
>>> kind(o)
'foo_bar'
>>> o.a
2
>>> o['a']
2
>>> o.double = property(lambda o: 2*o.a)
>>> o.double
4
>>> o['double']
4
>>> del o.a
>>> print(o.a)
None
>>> class Foo(Record):
...     full = property(lambda a: a.fname + ' ' + a.lname)
...
>>> f = Foo(fname='Joe', lname='Smith')
>>> f.full
'Joe Smith'
>>> f['full']
'Joe Smith'
>>> 'The name is %(full)s' % f
'The name is Joe Smith'
>>> getattr(f,'full')
'Joe Smith'
>>> print(Foo(_id=1, fname='Jane', lname='Smith'))
Foo
  fname ...............: 'Jane'
  lname ...............: 'Smith'
  full ................: 'Jane Smith'
>>> o = Record(a=2)
>>> o.a
2
>>> o.valid()
1
>>> o.attributes()
['a']
>>> o['a']
2
>>> o.double = property(lambda o: 2*o.a)
>>> o.double
4
>>> o['double']
4
>>> del o.a
>>> o.a
allows(user, action)
attributes()
get(name, *default)

Return the value for key if key is in the dictionary, else default.

save()

save record

valid()
class zoom.utils.RecordList(*a, **k)

Bases: list

a list of Records

class zoom.utils.Storage

Bases: dict

A Storage object is like a dictionary except obj.foo can be used in addition to obj[‘foo’].

>>> o = Storage(a=1)
>>> o.a
1
>>> o['a']
1
>>> o.a = 2
>>> o['a']
2
>>> del o.a
>>> o.a
zoom.utils.dedup(seq)

Remove duplicates while retaining order

zoom.utils.dictify(item)

prepare an object for transmission by marshalling it’s members

Marshals only members that json can handle.

>>> class Thing(object): pass
>>> pp(dictify(Bunch(name='Terry', age=21, funky_type=Thing())))
{
  "age": 21,
  "name": "Terry"
}
zoom.utils.existing(path, subdir=None)

Returns existing directories only

zoom.utils.generate_key()

make a new key

>>> len(generate_key())
40
zoom.utils.get_attributes(obj)
zoom.utils.get_config(filename)

load a config file into a Config object

>>> get_config('doesnt_exist.conf')
zoom.utils.id_for(*args)

Calculates a valid HTML tag id given an arbitrary string.

>>> id_for('Test 123')
'test-123'
>>> id_for('New Record')
'new-record'
>>> id_for('New "special" Record')
'new-special-record'
>>> id_for("hi", "test")
'hi~test'
>>> id_for("hi test")
'hi-test'
>>> id_for("hi-test")
'hi-test'
>>> id_for(1234)
'1234'
>>> id_for('this %$&#@^is##-$&*!it')
'this-is-it'
>>> id_for('test-this')
'test-this'
zoom.utils.kind(o)

returns a suitable table name for an object based on the object class

zoom.utils.locate_config(filename='zoom.conf', start='.')

locate a config file

First look in the current directory or above and then look in the user root directory and above.

zoom.utils.matches(item, terms)

Returns True if an item matches search terms

zoom.utils.name_for(text)

Calculates a valid HTML field name given an arbitrary string.

>>> name_for('Test 123')
'test_123'
>>> name_for('New Record')
'new_record'
>>> name_for('New "special" Record')
'new_special_record'
>>> name_for("hi test")
'hi_test'
>>> name_for("hi-test")
'hi_test'
>>> name_for(1234)
'1234'
>>> name_for('this %$&#@^is##-$&*!it')
'this_is_it'
>>> name_for('test-this')
'test_this'
zoom.utils.parents(path)
zoom.utils.pp(obj)

pretty print an object

>>> obj = dict(name='Joe', age=25)
>>> pp(obj)
{
  "age": 25,
  "name": "Joe"
}
zoom.utils.pretty(obj)

return an object in a pretty form

>>> obj = dict(name='Joe', age=25)
>>> pretty(obj)
'{\n  "age": 25,\n  "name": "Joe"\n}'
zoom.utils.search(items, text)

Returns items that match search terms

>>> items = [
...     {'name': 'Terry', 'instrument': 'guitar, drums, picolo', 'age': 25},
...     {'name': 'Pat', 'instrument': 'drums, vocals', 'age': 29},
...     {'name': 'Francis', 'instrument': 'saxophone, piano', 'age': 35},
... ]
>>> pp(list(search(items, 'drums')))
[
  {
    "age": 25,
    "instrument": "guitar, drums, picolo",
    "name": "Terry"
  },
  {
    "age": 29,
    "instrument": "drums, vocals",
    "name": "Pat"
  }
]
>>> pp(list(search(items, 'drums pat')))
[
  {
    "age": 29,
    "instrument": "drums, vocals",
    "name": "Pat"
  }
]
>>> pp(list(search(items, '')))
[
  {
    "age": 25,
    "instrument": "guitar, drums, picolo",
    "name": "Terry"
  },
  {
    "age": 29,
    "instrument": "drums, vocals",
    "name": "Pat"
  },
  {
    "age": 35,
    "instrument": "saxophone, piano",
    "name": "Francis"
  }
]
>>> pp(list(search(items, None)))
[
  {
    "age": 25,
    "instrument": "guitar, drums, picolo",
    "name": "Terry"
  },
  {
    "age": 29,
    "instrument": "drums, vocals",
    "name": "Pat"
  },
  {
    "age": 35,
    "instrument": "saxophone, piano",
    "name": "Francis"
  }
]
>>> sorted(list(search((list(item.values()) for item in items), '35'))[0], key=str)
[35, 'Francis', 'saxophone, piano']
zoom.utils.sorted_column_names(names)
zoom.utils.trim(text)

Remove the left most spaces for markdown

>>> trim('remove right ')
'remove right'
>>> trim(' remove left')
'remove left'
>>> print(trim(' remove spaces\n    from block\n    of text'))
remove spaces
   from block
   of text
>>> print(
...     trim(
...     '    \n'
...     '    remove spaces\n'
...     '        from block\n'
...     '        of text\n'
...     '    \n'
...     '\n'
...     )
... )

remove spaces
    from block
    of text

>>> print(trim('    remove spaces\n  from block\n  of text\n    '))
  remove spaces
from block
of text
>>> print(trim('    remove spaces\n  from block\n  of text'))
  remove spaces
from block
of text
>>> print(trim('\n  remove spaces\n    from block\n  of text'))

remove spaces
  from block
of text
>>> text = '\nremove spaces  \n    from block\nof text'
>>> print('\n'.join(repr(t) for t in trim(text).splitlines()))
'remove spaces  '
'    from block'
'of text'
>>> text = (
...     '\nremove spaces'
...     '\n    from block'
... )
>>> print(trim(text))
remove spaces
    from block

zoom.validators module

zoom.validators

class zoom.validators.Cleaner(transformer)

Bases: object

A content cleaner.

>>> Cleaner(str.lower).clean('Test')
'test'
>>> import decimal
>>> Cleaner(decimal.Decimal).clean('10')
Decimal('10')
clean(value)

cleans up a value

valid(value)

tests validity of a value

class zoom.validators.DateValidator(date_format='%b %d, %Y')

Bases: zoom.validators.Validator

Date validator

>>> v = DateValidator()
>>> v.valid('asdf')
False
>>> v.msg
'enter valid date in "Jan 31, 2016" format'
>>> v.valid('Jan 1, 2016')
True
>>> v.valid('Jan 41, 2016')
False
>>> v.valid('2016-01-14')
True
>>> v.valid('2016-01-41')
False
>>> v.valid(datetime.date(2016, 1, 14))
True
class zoom.validators.MaximumValue(max_value, empty_allowed=True)

Bases: zoom.validators.Validator

Maximum value validator

>>> v = MaximumValue(100)
>>> v.valid(50)
True
>>> v.valid(120)
False
>>> from datetime import date
>>> v = MaximumValue(date(2015,1,1))
>>> v.valid(date(2015,1,1))
True
>>> v.valid(date(2015,1,2))
False
>>> v.msg
'value must be at most 2015-01-01'
class zoom.validators.MinimumLength(min_length, empty_allowed=False)

Bases: zoom.validators.Validator

A minimum length validator

>>> v = MinimumLength(2)
>>> v.test('')
False
>>> v.test(' ')
False
>>> v.test('  ')
False
>>> v.test('t')
False
>>> v.msg
'minimum length 2'
>>> v.test('te')
True
>>> v = MinimumLength(2, True)
>>> v.test('')
True
>>> v.test(' ')
True
>>> v.test('  ')
True
>>> v.test('t')
False
>>> v.test('te')
True
class zoom.validators.MinimumValue(min_value, empty_allowed=True)

Bases: zoom.validators.Validator

Minimum value validator

>>> v = MinimumValue(100)
>>> v.valid(50)
False
>>> v.valid(120)
True
class zoom.validators.PostalCodeValidator

Bases: zoom.validators.RegexValidator

A Postal Code Validator

>>> validator = PostalCodeValidator()
>>> validator.valid('V8X 1G1')
True
>>> validator = PostalCodeValidator()
>>> validator.valid('V8X1G1')
True
>>> validator = PostalCodeValidator()
>>> validator.valid('V8X XG1')
False
>>> validator = PostalCodeValidator()
>>> validator.valid('8X XG1')
False
>>> validator = PostalCodeValidator()
>>> validator.valid('V8X 1g1')
True
class zoom.validators.RegexValidator(msg, regex, options=0)

Bases: zoom.validators.Validator

A regular expression validator

>>> validator = RegexValidator('invalid input', r'^[a-zA-Z0-9]+$')
>>> validator.valid('1')
True
>>> validator = RegexValidator('invalid input', r'^[a-zA-Z0-9]+$')
>>> validator.valid('')
True
>>> is_valid = RegexValidator('invalid input', r'^[a-zA-Z0-9]+$')
>>> is_valid('')
True
>>> is_valid('*')
False
>>> validator = RegexValidator('invalid input', r'^[a-zA-Z0-9]+$')
>>> validator.valid('-')
False
>>> validator.msg
'invalid input'
valid(value)

tests validity of a value

class zoom.validators.TimeValidator(time_format='%I:%M %p')

Bases: zoom.validators.Validator

Time validator

Validates a time in a variety of formats with time_format being the preferred format.

>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'C')
'C'
>>> v = TimeValidator()
>>> v.valid('asdf')
False
>>> v.msg
'enter valid time in 12 hour "02:20 PM" format'
>>> v.valid('10:20')
True
>>> v.valid('07:20')
True
>>> v.valid('7:20')
True
>>> v.valid('10:70')
False
>>> v.valid('14:20')
True
>>> v.valid('10:20 PM')
True
>>> v.valid('14:70')
False
>>> v.valid('10:20:10')
True
>>> v.valid('10:20:70')
False
>>> v.valid('10:70:20')
False
>>> v.valid(datetime.time(10, 20))
True
>>> v.valid(datetime.time(10, 20, 30))
True
valid_formats = ['%I:%M %p', '%I:%M:%S %p', '%H:%M', '%H:%M:%S']
class zoom.validators.URLValidator

Bases: zoom.validators.RegexValidator

A URL Validator

>>> validator = URLValidator()
>>> validator.valid('http://google.com')
True
>>> validator = URLValidator()
>>> validator.valid('test123')
False
class zoom.validators.Validator(msg, test)

Bases: object

A content validator.

>>> is_true = Validator('not true', bool)
>>> is_true.valid(1)
True
>>> is_true.valid([])
False
>>> is_true.msg
'not true'
>>> is_true.clean({})
{}
clean(value)

cleans up a value

valid(value)

tests validity of a value

zoom.validators.email_valid(email)

test for valid email address

>>> email_valid('test@testco.com')
True
>>> email_valid('test@@testco.com')
False
>>> email_valid('test@testco')
False
zoom.validators.empty(value)

test if a value is empty

>>> empty('')
True
>>> empty(' ')
True
>>> empty('\n')
True
>>> empty('x')
False
>>> empty(1)
False
zoom.validators.image_mime_type_valid(data)

check data against the more commonly browser supported mime types

zoom.validators.is_present(value)

test if a value is present

>>> is_present('')
False
>>> is_present('x')
True
zoom.validators.latitude_valid(value)

test for valid latitude

>>> latitude_valid(45)
True
>>> latitude_valid(100)
False
>>> latitude_valid('x')
False
zoom.validators.longitude_valid(value)

test for valid longitude

>>> longitude_valid(145)
True
>>> longitude_valid(200)
False
>>> longitude_valid('x')
False
zoom.validators.number_valid(value)

Test for valid number

>>> number_valid(0)
True
>>> number_valid(-1)
True
>>> number_valid(1.12039123)
True
>>> number_valid('1.12039123')
True
>>> number_valid('x1.12039123')
False
>>> number_valid('t')
False
>>> number_valid('')
True
>>> number_valid(False) # not sure if this is what's we want
True

Module contents

Zoom Web Framework