Merge pull request #410 from niphlod/docs/s-te
sphinx-compatible docstrings (7 modules remaining...)
This commit is contained in:
@@ -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
|
||||
-----------------------------------
|
||||
"""
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
12
gluon/sql.py
12
gluon/sql.py
@@ -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
|
||||
|
||||
365
gluon/sqlhtml.py
365
gluon/sqlhtml.py
@@ -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>)
|
||||
"""
|
||||
|
||||
175
gluon/storage.py
175
gluon/storage.py
@@ -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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user