Merge pull request #410 from niphlod/docs/s-te

sphinx-compatible docstrings (7 modules remaining...)
This commit is contained in:
mdipierro
2014-03-25 12:15:45 -05:00
9 changed files with 602 additions and 417 deletions

View File

@@ -1,15 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
::
# from http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942
# Title: Cross-site scripting (XSS) defense
# Submitter: Josh Goldfoot (other recipes)
# Last Updated: 2006/08/05
# Version no: 1.0
| From http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/496942
| Submitter: Josh Goldfoot (other recipes)
| Last Updated: 2006/08/05
| Version: 1.0
Cross-site scripting (XSS) defense
-----------------------------------
"""

View File

@@ -1,5 +1,13 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Background processes made simple
---------------------------------
"""
USAGE = """
## Example
@@ -118,6 +126,9 @@ CALLABLETYPES = (types.LambdaType, types.FunctionType,
class Task(object):
"""Defines a "task" object that gets passed from the main thread to the
executor's one
"""
def __init__(self, app, function, timeout, args='[]', vars='{}', **kwargs):
logger.debug(' new task allocated: %s.%s', app, function)
self.app = app
@@ -132,6 +143,9 @@ class Task(object):
class TaskReport(object):
"""Defines a "task report" object that gets passed from the executor's
thread to the main one
"""
def __init__(self, status, result=None, output=None, tb=None):
logger.debug(' new task report: %s', status)
if tb:
@@ -184,7 +198,7 @@ def _decode_dict(dct):
def executor(queue, task, out):
""" the background process """
"""The function used to execute tasks in the background process"""
logger.debug(' task started')
class LogOutput(object):
@@ -249,20 +263,28 @@ def executor(queue, task, out):
class MetaScheduler(threading.Thread):
"""Base class documenting scheduler's base methods"""
def __init__(self):
threading.Thread.__init__(self)
self.process = None # the background process
self.have_heartbeat = True # set to False to kill
self.empty_runs = 0
def async(self, task):
"""
starts the background process and returns:
('ok',result,output)
('error',exception,None)
('timeout',None,None)
('terminated',None,None)
"""Starts the background process
Args:
task : a `Task` object
Returns:
tuple: containing::
('ok',result,output)
('error',exception,None)
('timeout',None,None)
('terminated',None,None)
"""
db = self.db
sr = db.scheduler_run
@@ -332,22 +354,27 @@ class MetaScheduler(threading.Thread):
return tr
def die(self):
"""Forces termination of the worker process along with any running
task"""
logger.info('die!')
self.have_heartbeat = False
self.terminate_process()
def give_up(self):
"""Waits for any running task to be executed, then exits the worker
process"""
logger.info('Giving up as soon as possible!')
self.have_heartbeat = False
def terminate_process(self):
"""Terminates any running tasks (internal use only)"""
try:
self.process.terminate()
except:
pass # no process to terminate
def run(self):
""" the thread that sends heartbeat """
"""This is executed by the main thread to send heartbeats"""
counter = 0
while self.have_heartbeat:
self.send_heartbeat(counter)
@@ -361,6 +388,7 @@ class MetaScheduler(threading.Thread):
time.sleep(1)
def pop_task(self):
"""Fetches a task ready to be executed"""
return Task(
app=None,
function='demo_function',
@@ -369,6 +397,7 @@ class MetaScheduler(threading.Thread):
vars='{}')
def report_task(self, task, task_report):
"""Creates a task report"""
print 'reporting task'
pass
@@ -376,6 +405,8 @@ class MetaScheduler(threading.Thread):
pass
def loop(self):
"""Main loop, fetching tasks and starting executor's background
processes"""
try:
self.start_heartbeats()
while True and self.have_heartbeat:
@@ -406,7 +437,8 @@ WORKER_STATUS = (ACTIVE, PICK, DISABLED, TERMINATE, KILL, STOP_TASK)
class TYPE(object):
"""
validator that check whether field is valid json and validate its type
Validator that checks whether field is valid json and validates its type.
Used for `args` and `vars` of the scheduler_task table
"""
def __init__(self, myclass=list, parse=False):
@@ -430,6 +462,32 @@ class TYPE(object):
class Scheduler(MetaScheduler):
"""Scheduler object
Args:
db: DAL connection where Scheduler will create its tables
tasks(dict): either a dict containing name-->func or None.
If None, functions will be searched in the environment
migrate(bool): turn migration on/off for the Scheduler's tables
worker_name(str): force worker_name to identify each process.
Leave it to None to autoassign a name (hostname#pid)
group_names(list): process tasks belonging to this group
heartbeat(int): how many seconds the worker sleeps between one execution
and the following one. Indirectly sets how many seconds will pass
between checks for new tasks
max_empty_runs(int): how many loops are allowed to pass without
processing any tasks before exiting the process. 0 to keep always
the process alive
discard_results(bool): Scheduler stores executions's details into the
scheduler_run table. By default, only if there is a result the
details are kept. Turning this to True means discarding results
even for tasks that return something
utc_time(bool): do all datetime calculations assuming UTC as the
timezone. Remember to pass `start_time` and `stop_time` to tasks
accordingly
"""
def __init__(self, db, tasks=None, migrate=True,
worker_name=None, group_names=['main'], heartbeat=HEARTBEAT,
max_empty_runs=0, discard_results=False, utc_time=False):
@@ -467,9 +525,11 @@ class Scheduler(MetaScheduler):
return True
def now(self):
"""Shortcut that fetches current time based on UTC preferences"""
return self.utc_time and datetime.datetime.utcnow() or datetime.datetime.now()
def set_requirements(self, scheduler_task):
"""Called to set defaults for lazy_tables connections"""
from gluon import current
if hasattr(current, 'request'):
scheduler_task.application_name.default = '%s/%s' % (
@@ -477,6 +537,7 @@ class Scheduler(MetaScheduler):
)
def define_tables(self, db, migrate):
"""Defines Scheduler tables structure"""
from gluon.dal import DEFAULT
logger.debug('defining tables (migrate=%s)', migrate)
now = self.now
@@ -550,6 +611,23 @@ class Scheduler(MetaScheduler):
db.commit()
def loop(self, worker_name=None):
"""Main loop
This works basically as a neverending loop that:
- checks if the worker is ready to process tasks (is not DISABLED)
- pops a task from the queue
- if there is a task:
- spawns the executor background process
- waits for the process to be finished
- sleeps `heartbeat` seconds
- if there is not a task:
- checks for max_empty_runs
- sleeps `heartbeat` seconds
"""
signal.signal(signal.SIGTERM, lambda signum, stack_frame: sys.exit(1))
try:
self.start_heartbeats()
@@ -581,6 +659,10 @@ class Scheduler(MetaScheduler):
self.die()
def wrapped_assign_tasks(self, db):
"""Commodity function to call `assign_tasks` and trap exceptions
If an exception is raised, assume it happened because of database
contention and retries `assign_task` after 0.5 seconds
"""
logger.debug('Assigning tasks...')
db.commit() #db.commit() only for Mysql
x = 0
@@ -597,6 +679,10 @@ class Scheduler(MetaScheduler):
time.sleep(0.5)
def wrapped_pop_task(self):
"""Commodity function to call `pop_task` and trap exceptions
If an exception is raised, assume it happened because of database
contention and retries `pop_task` after 0.5 seconds
"""
db = self.db
db.commit() #another nifty db.commit() only for Mysql
x = 0
@@ -612,6 +698,7 @@ class Scheduler(MetaScheduler):
time.sleep(0.5)
def pop_task(self, db):
"""Grabs a task ready to be executed from the queue"""
now = self.now()
st = self.db.scheduler_task
if self.is_a_ticker and self.do_assign_tasks:
@@ -685,6 +772,8 @@ class Scheduler(MetaScheduler):
uuid=task.uuid)
def report_task(self, task, task_report):
"""Takes care of storing the result according to preferences
and deals with logic for repeating tasks"""
db = self.db
now = self.now()
while True:
@@ -744,12 +833,25 @@ class Scheduler(MetaScheduler):
time.sleep(0.5)
def adj_hibernation(self):
"""Used to increase the "sleep" interval for DISABLED workers"""
if self.worker_status[0] == DISABLED:
wk_st = self.worker_status[1]
hibernation = wk_st + 1 if wk_st < MAXHIBERNATION else MAXHIBERNATION
self.worker_status[1] = hibernation
def send_heartbeat(self, counter):
"""This function is vital for proper coordination among available
workers.
It:
- sends the heartbeat
- elects a ticker among available workers (the only process that
effectively dispatch tasks to workers)
- deals with worker's statuses
- does "housecleaning" for dead workers
- triggers tasks assignment to workers
"""
if not self.db_thread:
logger.debug('thread building own DAL object')
self.db_thread = DAL(
@@ -828,6 +930,10 @@ class Scheduler(MetaScheduler):
self.sleep()
def being_a_ticker(self):
"""Elects a TICKER process that assigns tasks to available workers.
Does its best to elect a worker that is not busy processing other tasks
to allow a proper distribution of tasks among all active workers ASAP
"""
db = self.db_thread
sw = db.scheduler_worker
all_active = db(
@@ -857,6 +963,11 @@ class Scheduler(MetaScheduler):
return False
def assign_tasks(self, db):
"""Assigns task to workers, that can then pop them from the queue
Deals with group_name(s) logic, in order to assign linearly tasks
to available workers for those groups
"""
sw, st = db.scheduler_worker, db.scheduler_task
now = self.now()
all_workers = db(sw.status == ACTIVE).select()
@@ -934,10 +1045,13 @@ class Scheduler(MetaScheduler):
logger.info('TICKER: tasks are %s', x)
def sleep(self):
"""Calculates the number of seconds to sleep according to worker's
status and `heartbeat` parameter"""
time.sleep(self.heartbeat * self.worker_status[1])
# should only sleep until next available task
def set_worker_status(self, group_names=None, action=ACTIVE):
"""Internal function to set worker's status"""
if not group_names:
group_names = self.group_names
elif isinstance(group_names, str):
@@ -948,32 +1062,47 @@ class Scheduler(MetaScheduler):
).update(status=action)
def disable(self, group_names=None):
"""Sets DISABLED on the workers processing `group_names` tasks.
A DISABLED worker will be kept alive but it won't be able to process
any waiting tasks, essentially putting it to sleep.
By default, all group_names of Scheduler's instantation are selected"""
self.set_worker_status(group_names=group_names,action=DISABLED)
def resume(self, group_names=None):
"""Wakes a worker up (it will be able to process queued tasks)"""
self.set_worker_status(group_names=group_names,action=ACTIVE)
def terminate(self, group_names=None):
"""Sets TERMINATE as worker status. The worker will wait for any
currently running tasks to be executed and then it will exit gracefully
"""
self.set_worker_status(group_names=group_names,action=TERMINATE)
def kill(self, group_names=None):
"""Sets KILL as worker status. The worker will be killed even if it's
processing a task."""
self.set_worker_status(group_names=group_names,action=KILL)
def queue_task(self, function, pargs=[], pvars={}, **kwargs):
"""
Queue tasks. This takes care of handling the validation of all
values.
:param function: the function (anything callable with a __name__)
:param pargs: "raw" args to be passed to the function. Automatically
jsonified.
:param pvars: "raw" kwargs to be passed to the function. Automatically
jsonified
:param kwargs: all the scheduler_task columns. args and vars here should be
in json format already, they will override pargs and pvars
parameters
returns a dict just as a normal validate_and_insert, plus a uuid key holding
the uuid of the queued task. If validation is not passed, both id and uuid
will be None, and you'll get an "error" dict holding the errors found.
Args:
function: the function (anything callable with a __name__)
pargs: "raw" args to be passed to the function. Automatically
jsonified.
pvars: "raw" kwargs to be passed to the function. Automatically
jsonified
kwargs: all the parameters available (basically, every
`scheduler_task` column). If args and vars are here, they should
be jsonified already, and they will override pargs and pvars
Returns:
a dict just as a normal validate_and_insert(), plus a uuid key
holding the uuid of the queued task. If validation is not passed
( i.e. some parameters are invalid) both id and uuid will be None,
and you'll get an "error" dict holding the errors found.
"""
if hasattr(function, '__name__'):
function = function.__name__
@@ -999,17 +1128,23 @@ class Scheduler(MetaScheduler):
def task_status(self, ref, output=False):
"""
Shortcut for task status retrieval
Retrieves task status and optionally the result of the task
:param ref: can be
- integer --> lookup will be done by scheduler_task.id
- string --> lookup will be done by scheduler_task.uuid
- query --> lookup as you wish (as in db.scheduler_task.task_name == 'test1')
:param output: fetch also the scheduler_run record
Args:
ref: can be
Returns a single Row object, for the last queued task
If output == True, returns also the last scheduler_run record
scheduler_run record is fetched by a left join, so it can
- an integer : lookup will be done by scheduler_task.id
- a string : lookup will be done by scheduler_task.uuid
- a `Query` : lookup as you wish, e.g. ::
db.scheduler_task.task_name == 'test1'
output(bool): if `True`, fetch also the scheduler_run record
Returns:
a single Row object, for the last queued task.
If output == True, returns also the last scheduler_run record.
The scheduler_run record is fetched by a left join, so it can
have all fields == None
"""
@@ -1044,21 +1179,27 @@ class Scheduler(MetaScheduler):
return row
def stop_task(self, ref):
"""
Experimental!!!
Shortcut for task termination.
If the task is RUNNING it will terminate it --> execution will be set as FAILED
If the task is QUEUED, its stop_time will be set as to "now",
the enabled flag will be set to False, status to STOPPED
"""Shortcut for task termination.
If the task is RUNNING it will terminate it, meaning that status
will be set as FAILED.
If the task is QUEUED, its stop_time will be set as to "now",
the enabled flag will be set to False, and the status to STOPPED
Args:
ref: can be
- an integer : lookup will be done by scheduler_task.id
- a string : lookup will be done by scheduler_task.uuid
:param ref: can be
- integer --> lookup will be done by scheduler_task.id
- string --> lookup will be done by scheduler_task.uuid
Returns:
- 1 if task was stopped (meaning an update has been done)
- None if task was not found, or if task was not RUNNING or QUEUED
Note:
Experimental
"""
from gluon.dal import Query
st, sw = self.db.scheduler_task, self.db.scheduler_worker
if isinstance(ref, int):
q = st.id == ref
@@ -1080,7 +1221,10 @@ class Scheduler(MetaScheduler):
def main():
"""
allows to run worker without python web2py.py .... by simply python this.py
allows to run worker without python web2py.py .... by simply::
python gluon/scheduler.py
"""
parser = optparse.OptionParser()
parser.add_option(

View File

@@ -1,7 +1,9 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
"""
import os

View File

@@ -2,11 +2,13 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>,
limodou <limodou@gmail.com> and srackham <srackham@gmail.com>.
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>,
| limodou <limodou@gmail.com> and srackham <srackham@gmail.com>.
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Web2py environment in the shell
--------------------------------
"""
import os
@@ -56,17 +58,13 @@ def exec_environment(
response=None,
session=None,
):
"""
.. function:: gluon.shell.exec_environment([pyfile=''[, request=Request()
[, response=Response[, session=Session()]]]])
"""Environment builder and module loader.
Environment builder and module loader.
Builds a web2py environment and optionally executes a Python file into
the environment.
Builds a web2py environment and optionally executes a Python
file into the environment.
A Storage dictionary containing the resulting environment is returned.
The working directory must be web2py root -- this is the web2py default.
A Storage dictionary containing the resulting environment is returned.
The working directory must be web2py root -- this is the web2py default.
"""
@@ -103,17 +101,15 @@ def env(
extra_request={},
):
"""
Return web2py execution environment for application (a), controller (c),
Returns web2py execution environment for application (a), controller (c),
function (f).
If import_models is True the exec all application models into the
environment.
extra_request allows you to pass along any extra
variables to the request object before your models
get executed. This was mainly done to support
web2py_utils.test_runner, however you can use it
with any wrapper scripts that need access to the
web2py environment.
extra_request allows you to pass along any extra variables to the request
object before your models get executed. This was mainly done to support
web2py_utils.test_runner, however you can use it with any wrapper scripts
that need access to the web2py environment.
"""
request = Request({})
@@ -197,8 +193,8 @@ def run(
Start interactive shell or run Python script (startfile) in web2py
controller environment. appname is formatted like:
a web2py application name
a/c exec the controller c into the application environment
- a : web2py application name
- a/c : exec the controller c into the application environment
"""
(a, c, f, args, vars) = parse_path_info(appname, av=True)
@@ -323,8 +319,8 @@ def run(
def parse_path_info(path_info, av=False):
"""
Parse path info formatted like a/c/f where c and f are optional
and a leading / accepted.
Parses path info formatted like a/c/f where c and f are optional
and a leading `/` is accepted.
Return tuple (a, c, f). If invalid path_info a is set to None.
If c or f are omitted they are set to None.
If av=True, parse args and vars
@@ -358,9 +354,9 @@ def test(testpath, import_models=True, verbose=False):
"""
Run doctests in web2py environment. testpath is formatted like:
a tests all controllers in application a
a/c tests controller c in application a
a/c/f test function f in controller c, application a
- a: tests all controllers in application a
- a/c: tests controller c in application a
- a/c/f test function f in controller c, application a
Where a, c and f are application, controller and function names
respectively. If the testpath is a file name the file is tested.

View File

@@ -1,5 +1,15 @@
# this file exists for backward compatibility
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
| This file is part of the web2py Web Framework
| Developed by Massimo Di Pierro <mdipierro@cs.depaul.edu>,
| limodou <limodou@gmail.com> and srackham <srackham@gmail.com>.
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Just for backward compatibility
--------------------------------
"""
__all__ = ['DAL', 'Field', 'DRIVERS']
from dal import DAL, Field, Table, Query, Set, Expression, Row, Rows, DRIVERS, BaseAdapter, SQLField, SQLTable, SQLXorable, SQLQuery, SQLSet, SQLRows, SQLStorage, SQLDB, GQLDB, SQLALL, SQLCustomType

View File

@@ -2,9 +2,9 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Holds:
@@ -13,6 +13,7 @@ Holds:
- form_factory: provides a SQLFORM for an non-db backed table
"""
import os
from gluon.http import HTTP
from gluon.html import XmlComponent
@@ -100,8 +101,8 @@ def show_if(cond):
class FormWidget(object):
"""
helper for SQLFORM to generate form input fields
(widget), related to the fieldtype
Helper for SQLFORM to generate form input fields (widget), related to the
fieldtype
"""
_class = 'generic-widget'
@@ -110,12 +111,12 @@ class FormWidget(object):
def _attributes(cls, field,
widget_attributes, **attributes):
"""
helper to build a common set of attributes
Helper to build a common set of attributes
:param field: the field involved,
some attributes are derived from this
:param widget_attributes: widget related attributes
:param attributes: any other supplied attributes
Args:
field: the field involved, some attributes are derived from this
widget_attributes: widget related attributes
attributes: any other supplied attributes
"""
attr = dict(
_id='%s_%s' % (field.tablename, field.name),
@@ -135,7 +136,7 @@ class FormWidget(object):
@classmethod
def widget(cls, field, value, **attributes):
"""
generates the widget for the field.
Generates the widget for the field.
When serialized, will provide an INPUT tag:
@@ -143,9 +144,10 @@ class FormWidget(object):
- class = field.type
- name = fieldname
:param field: the field needing the widget
:param value: value
:param attributes: any other attributes to be applied
Args:
field: the field needing the widget
value: value
attributes: any other attributes to be applied
"""
raise NotImplementedError
@@ -157,9 +159,9 @@ class StringWidget(FormWidget):
@classmethod
def widget(cls, field, value, **attributes):
"""
generates an INPUT text tag.
Generates an INPUT text tag.
see also: :meth:`FormWidget.widget`
see also: `FormWidget.widget`
"""
default = dict(
@@ -199,9 +201,9 @@ class TextWidget(FormWidget):
@classmethod
def widget(cls, field, value, **attributes):
"""
generates a TEXTAREA tag.
Generates a TEXTAREA tag.
see also: :meth:`FormWidget.widget`
see also: `FormWidget.widget`
"""
default = dict(value=value)
@@ -214,9 +216,9 @@ class JSONWidget(FormWidget):
@classmethod
def widget(cls, field, value, **attributes):
"""
generates a TEXTAREA for JSON notation.
Generates a TEXTAREA for JSON notation.
see also: :meth:`FormWidget.widget`
see also: `FormWidget.widget`
"""
if not isinstance(value, basestring):
if value is not None:
@@ -231,9 +233,9 @@ class BooleanWidget(FormWidget):
@classmethod
def widget(cls, field, value, **attributes):
"""
generates an INPUT checkbox tag.
Generates an INPUT checkbox tag.
see also: :meth:`FormWidget.widget`
see also: `FormWidget.widget`
"""
default = dict(_type='checkbox', value=value)
@@ -247,10 +249,13 @@ class OptionsWidget(FormWidget):
@staticmethod
def has_options(field):
"""
checks if the field has selectable options
Checks if the field has selectable options
:param field: the field needing checking
:returns: True if the field has options
Args:
field: the field needing checking
Returns:
True if the field has options
"""
return hasattr(field.requires, 'options')
@@ -258,9 +263,9 @@ class OptionsWidget(FormWidget):
@classmethod
def widget(cls, field, value, **attributes):
"""
generates a SELECT tag, including OPTIONs (only 1 option allowed)
Generates a SELECT tag, including OPTIONs (only 1 option allowed)
see also: :meth:`FormWidget.widget`
see also: `FormWidget.widget`
"""
default = dict(value=value)
attr = cls._attributes(field, default,
@@ -307,12 +312,13 @@ class MultipleOptionsWidget(OptionsWidget):
@classmethod
def widget(cls, field, value, size=5, **attributes):
"""
generates a SELECT tag, including OPTIONs (multiple options allowed)
Generates a SELECT tag, including OPTIONs (multiple options allowed)
see also: :meth:`FormWidget.widget`
see also: `FormWidget.widget`
:param size: optional param (default=5) to indicate how many rows must
be shown
Args:
size: optional param (default=5) to indicate how many rows must
be shown
"""
attributes.update(_size=size, _multiple=True)
@@ -325,9 +331,9 @@ class RadioWidget(OptionsWidget):
@classmethod
def widget(cls, field, value, **attributes):
"""
generates a TABLE tag, including INPUT radios (only 1 option allowed)
Generates a TABLE tag, including INPUT radios (only 1 option allowed)
see also: :meth:`FormWidget.widget`
see also: `FormWidget.widget`
"""
if isinstance(value, (list,tuple)):
@@ -389,9 +395,9 @@ class CheckboxesWidget(OptionsWidget):
@classmethod
def widget(cls, field, value, **attributes):
"""
generates a TABLE tag, including INPUT checkboxes (multiple allowed)
Generates a TABLE tag, including INPUT checkboxes (multiple allowed)
see also: :meth:`FormWidget.widget`
see also: `FormWidget.widget`
"""
# was values = re.compile('[\w\-:]+').findall(str(value))
@@ -466,11 +472,11 @@ class PasswordWidget(FormWidget):
@classmethod
def widget(cls, field, value, **attributes):
"""
generates a INPUT password tag.
Generates a INPUT password tag.
If a value is present it will be shown as a number of '*', not related
to the length of the actual value.
see also: :meth:`FormWidget.widget`
see also: `FormWidget.widget`
"""
# detect if attached a IS_STRONG with entropy
default = dict(
@@ -506,11 +512,15 @@ class UploadWidget(FormWidget):
Optionally provides an A link to the file, including a checkbox so
the file can be deleted.
All is wrapped in a DIV.
see also: :meth:`FormWidget.widget`
see also: `FormWidget.widget`
:param download_url: Optional URL to link to the file (default = None)
Args:
field: the field
value: the field value
download_url: url for the file download (default = None)
"""
default = dict(_type='file',)
@@ -554,15 +564,16 @@ class UploadWidget(FormWidget):
@classmethod
def represent(cls, field, value, download_url=None):
"""
how to represent the file:
How to represent the file:
- with download url and if it is an image: <A href=...><IMG ...></A>
- otherwise with download url: <A href=...>file</A>
- otherwise: file
:param field: the field
:param value: the field value
:param download_url: url for the file download (default = None)
Args:
field: the field
value: the field value
download_url: url for the file download (default = None)
"""
inp = current.T(cls.GENERIC_DESCRIPTION)
@@ -586,7 +597,8 @@ class UploadWidget(FormWidget):
Checking is based on filename extension. Currently recognized:
gif, png, jp(e)g, bmp
:param value: filename
Args:
value: filename
"""
extension = value.split('.')[-1].lower()
@@ -705,7 +717,7 @@ class AutocompleteWidget(object):
def formstyle_table3cols(form, fields):
''' 3 column table - default '''
""" 3 column table - default """
table = TABLE()
for id, label, controls, help in fields:
_help = TD(help, _class='w2p_fc')
@@ -716,7 +728,7 @@ def formstyle_table3cols(form, fields):
def formstyle_table2cols(form, fields):
''' 2 column table '''
""" 2 column table """
table = TABLE()
for id, label, controls, help in fields:
_help = TD(help, _class='w2p_fc', _width='50%')
@@ -728,7 +740,7 @@ def formstyle_table2cols(form, fields):
def formstyle_divs(form, fields):
''' divs only '''
""" divs only """
table = FIELDSET()
for id, label, controls, help in fields:
_help = DIV(help, _class='w2p_fc')
@@ -739,7 +751,7 @@ def formstyle_divs(form, fields):
def formstyle_inline(form, fields):
''' divs only '''
""" divs only, but inline """
if len(fields) != 2:
raise RuntimeError("Not possible")
id, label, controls, help = fields[0]
@@ -749,7 +761,7 @@ def formstyle_inline(form, fields):
def formstyle_ul(form, fields):
''' unordered list '''
""" unordered list """
table = UL()
for id, label, controls, help in fields:
_help = DIV(help, _class='w2p_fc')
@@ -760,7 +772,7 @@ def formstyle_ul(form, fields):
def formstyle_bootstrap(form, fields):
''' bootstrap format form layout '''
""" bootstrap 2.3.x format form layout """
form.add_class('form-horizontal')
parent = FIELDSET()
for id, label, controls, help in fields:
@@ -804,7 +816,11 @@ def formstyle_bootstrap(form, fields):
return parent
def formstyle_bootstrap3(form, fields):
''' bootstrap 3 format form layout '''
""" bootstrap 3 format form layout
Note:
Experimental!
"""
form.add_class('form-horizontal')
parent = FIELDSET()
for id, label, controls, help in fields:
@@ -862,43 +878,51 @@ def formstyle_bootstrap3(form, fields):
class SQLFORM(FORM):
"""
SQLFORM is used to map a table (and a current record) into an HTML form
SQLFORM is used to map a table (and a current record) into an HTML form.
given a SQLTable stored in db.table
Given a Table like db.table
generates an insert form::
Generates an insert form::
SQLFORM(db.table)
generates an update form::
Generates an update form::
record=db.table[some_id]
SQLFORM(db.table, record)
generates an update with a delete button::
Generates an update with a delete button::
SQLFORM(db.table, record, deletable=True)
if record is an int::
record=db.table[record]
optional arguments:
:param fields: a list of fields that should be placed in the form,
default is all.
:param labels: a dictionary with labels for each field, keys are the field
names.
:param col3: a dictionary with content for an optional third column
Args:
table: `Table` object
record: either an int if the `id` is an int, or the record fetched
from the table
deletable: adds the delete checkbox
linkto: the URL of a controller/function to access referencedby
records
upload: the URL of a controller/function to download an uploaded file
fields: a list of fields that should be placed in the form,
default is all.
labels: a dictionary with labels for each field, keys are the field
names.
col3: a dictionary with content for an optional third column
(right of each field). keys are field names.
:param linkto: the URL of a controller/function to access referencedby
records
see controller appadmin.py for examples
:param upload: the URL of a controller/function to download an uploaded file
see controller appadmin.py for examples
submit_button: text to show in the submit button
delete_label: text to show next to the delete checkbox
showid: shows the id of the record
readonly: doesn't allow for any modification
comments: show comments (stored in `col3` or in Field definition)
ignore_rw: overrides readable/writable attributes
record_id: used to create session key against CSRF
formstyle: what to use to generate the form layout
buttons: override buttons as you please (will be also stored in
`form.custom.submit`)
separator: character as separator between labels and inputs
any named optional attribute is passed to the <form> tag
for example _class, _id, _style, _action, _method, etc.
for example _class, _id, _style, _action, _method, etc.
"""
@@ -987,13 +1011,6 @@ class SQLFORM(FORM):
separator=': ',
**attributes
):
"""
SQLFORM(db.table,
record=None,
fields=['name'],
labels={'name': 'Your name'},
linkto=URL(f='table/db/')
"""
T = current.T
self.ignore_rw = ignore_rw
@@ -1298,12 +1315,15 @@ class SQLFORM(FORM):
):
"""
similar FORM.accepts but also does insert, update or delete in DAL.
but if detect_record_change == True than:
form.record_changed = False (record is properly validated/submitted)
form.record_changed = True (record cannot be submitted because changed)
elseif detect_record_change == False than:
form.record_changed = None
Similar to `FORM.accepts` but also does insert, update or delete in DAL.
If detect_record_change is `True` than:
- `form.record_changed = False` (record is properly validated/submitted)
- `form.record_changed = True` (record cannot be submitted because changed)
If detect_record_change == False than:
- `form.record_changed = None`
"""
if keepvalues is None:
@@ -1391,10 +1411,10 @@ class SQLFORM(FORM):
if self.record_changed and self.detect_record_change:
message_onchange = \
kwargs.setdefault("message_onchange",
current.T("A record change was detected. " +
"Consecutive update self-submissions " +
"are not allowed. Try re-submitting or " +
"refreshing the form page."))
current.T("A record change was detected. " +
"Consecutive update self-submissions " +
"are not allowed. Try re-submitting or " +
"refreshing the form page."))
if message_onchange is not None:
current.response.flash = message_onchange
return ret
@@ -1633,7 +1653,7 @@ class SQLFORM(FORM):
@staticmethod
def factory(*fields, **attributes):
"""
generates a SQLFORM for the given fields.
Generates a SQLFORM for the given fields.
Internally will build a non-database based data model
to hold the fields.
@@ -2047,10 +2067,11 @@ class SQLFORM(FORM):
return buttons
def linsert(lst, i, x):
"""
a = [1,2]
linsert(a, 1, [0,3])
a = [1, 0, 3, 2]
"""Internal use only: inserts x list into lst at i pos::
a = [1,2]
linsert(a, 1, [0,3])
a = [1, 0, 3, 2]
"""
lst[i:i] = x
@@ -2455,7 +2476,6 @@ class SQLFORM(FORM):
cols = [COL(_id=str(c).replace('.', '-'),
data={'column': left_cols + i + 1})
for i,c in enumerate(columns)]
n = len(head.components)
cols = [COL(data={'column': i + 1}) for i in range(left_cols)] + \
cols + \
[COL(data={'column': left_cols + len(cols) + i + 1})
@@ -2636,33 +2656,36 @@ class SQLFORM(FORM):
divider='>', breadcrumbs_class='',
**kwargs):
"""
@auth.requires_login()
def index():
db.define_table('person',Field('name'),format='%(name)s')
db.define_table('dog',
Field('name'),Field('owner',db.person),format='%(name)s')
db.define_table('comment',Field('body'),Field('dog',db.dog))
if db(db.person).isempty():
from gluon.contrib.populate import populate
populate(db.person,300)
populate(db.dog,300)
populate(db.comment,1000)
db.commit()
form=SQLFORM.smartgrid(db[request.args(0) or 'person']) #***
return dict(form=form)
Builds a system of SQLFORM.grid(s) between any referenced tables
*** builds a complete interface to navigate all tables links
to the request.args(0)
table: pagination, search, view, edit, delete,
children, parent, etc.
Args:
table: main table
constraints(dict): `{'table':query}` that limits which records can
be accessible
links(dict): like `{'tablename':[lambda row: A(....), ...]}` that
will add buttons when table tablename is displayed
linked_tables(list): list of tables to be linked
Example:
given you defined a model as::
db.define_table('person',Field('name'),format='%(name)s')
db.define_table('dog',
Field('name'),Field('owner',db.person),format='%(name)s')
db.define_table('comment',Field('body'),Field('dog',db.dog))
if db(db.person).isempty():
from gluon.contrib.populate import populate
populate(db.person,300)
populate(db.dog,300)
populate(db.comment,1000)
in a controller, you can do::
@auth.requires_login()
def index():
form=SQLFORM.smartgrid(db[request.args(0) or 'person'])
return dict(form=form)
constraints is a dict {'table':query} that limits which
records can be accessible
links is a dict like
{'tablename':[lambda row: A(....), ...]}
that will add buttons when table tablename is displayed
linked_tables is a optional list of tablenames of tables
to be linked
"""
request, T = current.request, current.T
if args is None:
@@ -2829,76 +2852,39 @@ class SQLFORM(FORM):
class SQLTABLE(TABLE):
"""
given a Rows object, as returned by a db().select(), generates
Given with a Rows object, as returned by a `db().select()`, generates
an html table with the rows.
optional arguments:
:param linkto: URL (or lambda to generate a URL) to edit individual records
:param upload: URL to download uploaded files
:param orderby: Add an orderby link to column headers.
:param headers: dictionary of headers to headers redefinions
headers can also be a string to gerenare the headers from data
for now only headers="fieldname:capitalize",
headers="labels" and headers=None are supported
:param truncate: length at which to truncate text in table cells.
Defaults to 16 characters.
:param columns: a list or dict contaning the names of the columns to be shown
Defaults to all
Optional names attributes for passed to the <table> tag
The keys of headers and columns must be of the form "tablename.fieldname"
Simple linkto example::
rows = db.select(db.sometable.ALL)
table = SQLTABLE(rows, linkto='someurl')
This will link rows[id] to .../sometable/value_of_id
Args:
sqlrows : the `Rows` object
linkto: URL (or lambda to generate a URL) to edit individual records
upload: URL to download uploaded files
orderby: Add an orderby link to column headers.
headers: dictionary of headers to headers redefinions
headers can also be a string to gerenare the headers from data
for now only headers="fieldname:capitalize",
headers="labels" and headers=None are supported
truncate: length at which to truncate text in table cells.
Defaults to 16 characters.
columns: a list or dict contaning the names of the columns to be shown
Defaults to all
th_link: base link to support orderby headers
extracolumns: a list of dicts
selectid: The id you want to select
renderstyle: Boolean render the style with the table
cid: use this cid for all links
colgroup: #FIXME
More advanced linkto example::
Extracolumns example
::
def mylink(field, type, ref):
return URL(args=[field])
[{'label':A('Extra',_href='#'),
'class': '', #class name of the header
'width':'', #width in pixels or %
'content':lambda row, rc: A('Edit',_href='edit/%s'%row.id),
'selected': False #agregate class selected to this column}]
rows = db.select(db.sometable.ALL)
table = SQLTABLE(rows, linkto=mylink)
This will link rows[id] to
current_app/current_controlle/current_function/value_of_id
New Implements: 24 June 2011:
-----------------------------
:param selectid: The id you want to select
:param renderstyle: Boolean render the style with the table
:param extracolumns = [{'label':A('Extra',_href='#'),
'class': '', #class name of the header
'width':'', #width in pixels or %
'content':lambda row, rc: A('Edit',_href='edit/%s'%row.id),
'selected': False #agregate class selected to this column
}]
:param headers = {'table.id':{'label':'Id',
'class':'', #class name of the header
'width':'', #width in pixels or %
'truncate': 16, #truncate the content to...
'selected': False #agregate class selected to this column
},
'table.myfield':{'label':'My field',
'class':'', #class name of the header
'width':'', #width in pixels or %
'truncate': 16, #truncate the content to...
'selected': False #agregate class selected to this column
},
}
table = SQLTABLE(rows, headers=headers, extracolumns=extracolumns)
`<
"""
@@ -2948,7 +2934,7 @@ class SQLTABLE(TABLE):
cols += [COL(data={'column': len(cols) + i + 1})
for i, c in enumerate(extracolumns)]
components.append(COLGROUP(*cols))
if headers is None:
headers = {}
else:
@@ -3099,7 +3085,7 @@ class SQLTABLE(TABLE):
def style(self):
css = '''
css = """
table tbody tr.w2p_odd {
background-color: #DFD;
}
@@ -3115,7 +3101,7 @@ class SQLTABLE(TABLE):
table tbody tr:hover {
background: #DDF;
}
'''
"""
return css
@@ -3133,7 +3119,8 @@ class ExportClass(object):
def represented(self):
def none_exception(value):
"""
returns a cleaned up value that can be used for csv export:
Returns a cleaned up value that can be used for csv export:
- unicode text is encoded as such
- None values are replaced with the given representation (default <NULL>)
"""

View File

@@ -2,9 +2,9 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Provides:
@@ -25,6 +25,8 @@ class Storage(dict):
A Storage object is like a dictionary except `obj.foo` can be used
in addition to `obj['foo']`, and setting obj.foo = None deletes item foo.
Example::
>>> o = Storage(a=1)
>>> print o.a
1
@@ -39,6 +41,7 @@ class Storage(dict):
>>> del o.a
>>> print o.a
None
"""
__slots__ = ()
__setattr__ = dict.__setitem__
@@ -52,23 +55,25 @@ class Storage(dict):
def getlist(self, key):
"""
Return a Storage value as a list.
Returns a Storage value as a list.
If the value is a list it will be returned as-is.
If object is None, an empty list will be returned.
Otherwise, [value] will be returned.
Otherwise, `[value]` will be returned.
Example output for a query string of `?x=abc&y=abc&y=def`::
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getlist('x')
['abc']
>>> request.vars.getlist('y')
['abc', 'def']
>>> request.vars.getlist('z')
[]
Example output for a query string of ?x=abc&y=abc&y=def
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getlist('x')
['abc']
>>> request.vars.getlist('y')
['abc', 'def']
>>> request.vars.getlist('z')
[]
"""
value = self.get(key, [])
if value is None or isinstance(value, (list, tuple)):
@@ -78,43 +83,48 @@ class Storage(dict):
def getfirst(self, key, default=None):
"""
Return the first or only value when given a request.vars-style key.
Returns the first value of a list or the value itself when given a
`request.vars` style key.
If the value is a list, its first item will be returned;
otherwise, the value will be returned as-is.
Example output for a query string of ?x=abc&y=abc&y=def
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getfirst('x')
'abc'
>>> request.vars.getfirst('y')
'abc'
>>> request.vars.getfirst('z')
Example output for a query string of `?x=abc&y=abc&y=def`::
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getfirst('x')
'abc'
>>> request.vars.getfirst('y')
'abc'
>>> request.vars.getfirst('z')
"""
values = self.getlist(key)
return values[0] if values else default
def getlast(self, key, default=None):
"""
Returns the last or only single value when
given a request.vars-style key.
Returns the last value of a list or value itself when given a
`request.vars` style key.
If the value is a list, the last item will be returned;
otherwise, the value will be returned as-is.
Simulated output with a query string of ?x=abc&y=abc&y=def
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getlast('x')
'abc'
>>> request.vars.getlast('y')
'def'
>>> request.vars.getlast('z')
Simulated output with a query string of `?x=abc&y=abc&y=def`::
>>> request = Storage()
>>> request.vars = Storage()
>>> request.vars.x = 'abc'
>>> request.vars.y = ['abc', 'def']
>>> request.vars.getlast('x')
'abc'
>>> request.vars.getlast('y')
'def'
>>> request.vars.getlast('z')
"""
values = self.getlist(key)
return values[-1] if values else default
@@ -124,7 +134,7 @@ PICKABLE = (str, int, long, float, bool, list, dict, tuple, set)
class StorageList(Storage):
"""
like Storage but missing elements default to [] instead of None
Behaves like Storage but missing elements defaults to [] instead of None
"""
def __getitem__(self, key):
return self.__getattr__(key)
@@ -183,35 +193,36 @@ class FastStorage(dict):
Eventually this should replace class Storage but causes memory leak
because of http://bugs.python.org/issue1469629
>>> s = FastStorage()
>>> s.a = 1
>>> s.a
1
>>> s['a']
1
>>> s.b
>>> s['b']
>>> s['b']=2
>>> s['b']
2
>>> s.b
2
>>> isinstance(s,dict)
True
>>> dict(s)
{'a': 1, 'b': 2}
>>> dict(FastStorage(s))
{'a': 1, 'b': 2}
>>> import pickle
>>> s = pickle.loads(pickle.dumps(s))
>>> dict(s)
{'a': 1, 'b': 2}
>>> del s.b
>>> del s.a
>>> s.a
>>> s.b
>>> s['a']
>>> s['b']
>>> s = FastStorage()
>>> s.a = 1
>>> s.a
1
>>> s['a']
1
>>> s.b
>>> s['b']
>>> s['b']=2
>>> s['b']
2
>>> s.b
2
>>> isinstance(s,dict)
True
>>> dict(s)
{'a': 1, 'b': 2}
>>> dict(FastStorage(s))
{'a': 1, 'b': 2}
>>> import pickle
>>> s = pickle.loads(pickle.dumps(s))
>>> dict(s)
{'a': 1, 'b': 2}
>>> del s.b
>>> del s.a
>>> s.a
>>> s.b
>>> s['a']
>>> s['b']
"""
def __init__(self, *args, **kwargs):
dict.__init__(self, *args, **kwargs)
@@ -246,14 +257,30 @@ class FastStorage(dict):
class List(list):
"""
Like a regular python list but a[i] if i is out of bounds return None
instead of IndexOutOfBounds
Like a regular python list but a[i] if i is out of bounds returns None
instead of `IndexOutOfBounds`
"""
def __call__(self, i, default=DEFAULT, cast=None, otherwise=None):
"""
request.args(0,default=0,cast=int,otherwise='http://error_url')
request.args(0,default=0,cast=int,otherwise=lambda:...)
"""Allows to use a special syntax for fast-check of `request.args()`
validity
Args:
i: index
default: use this value if arg not found
cast: type cast
otherwise: can be:
- None: results in a 404
- str: redirect to this address
- callable: calls the function (nothing is passed)
Example:
You can use::
request.args(0,default=0,cast=int,otherwise='http://error_url')
request.args(0,default=0,cast=int,otherwise=lambda:...)
"""
n = len(self)
if 0 <= i < n or -n <= i < 0:

View File

@@ -2,9 +2,12 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework
Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| Copyrighted by Massimo Di Pierro <mdipierro@cs.depaul.edu>
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
Facilities to handle file streaming
------------------------------------
"""
import os

View File

@@ -2,16 +2,16 @@
# -*- coding: utf-8 -*-
"""
This file is part of the web2py Web Framework (Copyrighted, 2007-2011).
License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| This file is part of the web2py Web Framework
| License: LGPLv3 (http://www.gnu.org/licenses/lgpl.html)
| Author: Thadeus Burgess
| Contributors:
| - Massimo Di Pierro for creating the original gluon/template.py
| - Jonathan Lundell for extensively testing the regex on Jython.
| - Limodou (creater of uliweb) who inspired the block-element support for web2py.
Author: Thadeus Burgess
Contributors:
- Thank you to Massimo Di Pierro for creating the original gluon/template.py
- Thank you to Jonathan Lundell for extensively testing the regex on Jython.
- Thank you to Limodou (creater of uliweb) who inspired the block-element support for web2py.
Templating syntax
------------------
"""
import os
@@ -90,6 +90,7 @@ class BlockNode(Node):
{{ block test }}
This is default block test
{{ end }}
"""
def __init__(self, name='', pre_extend=False, delimiters=('{{', '}}')):
"""
@@ -115,11 +116,10 @@ class BlockNode(Node):
def append(self, node):
"""
Add an element to the nodes.
Adds an element to the nodes.
Keyword Arguments
- node -- Node object or string to append.
Args:
node: Node object or string to append.
"""
if isinstance(node, str) or isinstance(node, Node):
self.nodes.append(node)
@@ -128,11 +128,10 @@ class BlockNode(Node):
def extend(self, other):
"""
Extend the list of nodes with another BlockNode class.
Extends the list of nodes with another BlockNode class.
Keyword Arguments
- other -- BlockNode or Content object to extend from.
Args:
other: BlockNode or Content object to extend from.
"""
if isinstance(other, BlockNode):
self.nodes.extend(other.nodes)
@@ -143,8 +142,9 @@ class BlockNode(Node):
def output(self, blocks):
"""
Merges all nodes into a single string.
blocks -- Dictionary of blocks that are extending
from this template.
Args:
blocks: Dictionary of blocks that are extending from this template.
"""
return ''.join(output_aux(node, blocks) for node in self.nodes)
@@ -154,13 +154,11 @@ class Content(BlockNode):
Parent Container -- Used as the root level BlockNode.
Contains functions that operate as such.
Args:
name: Unique name for this BlockNode
"""
def __init__(self, name="ContentBlock", pre_extend=False):
"""
Keyword Arguments
name -- Unique name for this BlockNode
"""
self.name = name
self.nodes = []
self.blocks = {}
@@ -220,6 +218,21 @@ class Content(BlockNode):
class TemplateParser(object):
"""Parse all blocks
Args:
text: text to parse
context: context to parse in
path: folder path to templates
writer: string of writer class to use
lexers: dict of custom lexers to use.
delimiters: for example `('{{','}}')`
_super_nodes: a list of nodes to check for inclusion
this should only be set by "self.extend"
It contains a list of SuperNodes from a child
template that need to be handled.
"""
default_delimiters = ('{{', '}}')
r_tag = compile(r'(\{\{.*?\}\})', DOTALL)
@@ -244,18 +257,6 @@ class TemplateParser(object):
delimiters=('{{', '}}'),
_super_nodes = [],
):
"""
text -- text to parse
context -- context to parse in
path -- folder path to templates
writer -- string of writer class to use
lexers -- dict of custom lexers to use.
delimiters -- for example ('{{','}}')
_super_nodes -- a list of nodes to check for inclusion
this should only be set by "self.extend"
It contains a list of SuperNodes from a child
template that need to be handled.
"""
# Keep a root level name.
self.name = name
@@ -317,18 +318,18 @@ class TemplateParser(object):
def to_string(self):
"""
Return the parsed template with correct indentation.
Returns the parsed template with correct indentation.
Used to make it easier to port to python3.
"""
return self.reindent(str(self.content))
def __str__(self):
"Make sure str works exactly the same as python 3"
"Makes sure str works exactly the same as python 3"
return self.to_string()
def __unicode__(self):
"Make sure str works exactly the same as python 3"
"Makes sure str works exactly the same as python 3"
return self.to_string()
def reindent(self, text):
@@ -411,13 +412,13 @@ class TemplateParser(object):
def _raise_error(self, message='', text=None):
"""
Raise an error using itself as the filename and textual content.
Raises an error using itself as the filename and textual content.
"""
raise RestrictedError(self.name, text or self.text, message)
def _get_file_text(self, filename):
"""
Attempt to open ``filename`` and retrieve its text.
Attempts to open ``filename`` and retrieve its text.
This will use self.path to search for the file.
"""
@@ -454,7 +455,7 @@ class TemplateParser(object):
def include(self, content, filename):
"""
Include ``filename`` here.
Includes ``filename`` here.
"""
text = self._get_file_text(filename)
@@ -469,8 +470,8 @@ class TemplateParser(object):
def extend(self, filename):
"""
Extend ``filename``. Anything not declared in a block defined by the
parent will be placed in the parent templates ``{{include}}`` block.
Extends `filename`. Anything not declared in a block defined by the
parent will be placed in the parent templates `{{include}}` block.
"""
# If no filename, create a dummy layout with only an {{include}}.
text = self._get_file_text(filename) or '%sinclude%s' % tuple(self.delimiters)
@@ -770,9 +771,12 @@ def parse_template(filename,
delimiters=('{{', '}}')
):
"""
filename can be a view filename in the views folder or an input stream
path is the path of a views folder
context is a dictionary of symbols used to render the template
Args:
filename: can be a view filename in the views folder or an input stream
path: is the path of a views folder
context: is a dictionary of symbols used to render the template
lexers: dict of custom lexers to use
delimiters: opening and closing tags
"""
# First, if we have a str try to open the file
@@ -841,30 +845,44 @@ def render(content="hello world",
writer='response.write'
):
"""
>>> render()
'hello world'
>>> render(content='abc')
'abc'
>>> render(content='abc\'')
"abc'"
>>> render(content='a"\'bc')
'a"\\'bc'
>>> render(content='a\\nbc')
'a\\nbc'
>>> render(content='a"bcd"e')
'a"bcd"e'
>>> render(content="'''a\\nc'''")
"'''a\\nc'''"
>>> render(content="'''a\\'c'''")
"'''a\'c'''"
>>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5))
'0<br />1<br />2<br />3<br />4<br />'
>>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}'))
'0<br />1<br />2<br />3<br />4<br />'
>>> render(content="{{='''hello\\nworld'''}}")
'hello\\nworld'
>>> render(content='{{for i in range(3):\\n=i\\npass}}')
'012'
Generic render function
Args:
content: default content
stream: file-like obj to read template from
filename: where to find template
path: base path for templates
context: env
lexers: custom lexers to use
delimiters: opening and closing tags
writer: where to inject the resulting stream
Example::
>>> render()
'hello world'
>>> render(content='abc')
'abc'
>>> render(content="abc'")
"abc'"
>>> render(content=''''a"'bc''')
'a"'bc'
>>> render(content='a\\nbc')
'a\\nbc'
>>> render(content='a"bcd"e')
'a"bcd"e'
>>> render(content="'''a\\nc'''")
"'''a\\nc'''"
>>> render(content="'''a\\'c'''")
"'''a\'c'''"
>>> render(content='{{for i in range(a):}}{{=i}}<br />{{pass}}', context=dict(a=5))
'0<br />1<br />2<br />3<br />4<br />'
>>> render(content='{%for i in range(a):%}{%=i%}<br />{%pass%}', context=dict(a=5),delimiters=('{%','%}'))
'0<br />1<br />2<br />3<br />4<br />'
>>> render(content="{{='''hello\\nworld'''}}")
'hello\\nworld'
>>> render(content='{{for i in range(3):\\n=i\\npass}}')
'012'
"""
# here to avoid circular Imports
try: