Compare commits

..

55 Commits

Author SHA1 Message Date
mdipierro
d93a13ca46 restoring commented tests 2019-01-01 22:13:54 -08:00
mdipierro
7216003eb6 to_native missing, why this was not failing locally 2019-01-01 22:10:53 -08:00
mdipierro
e16becb6a9 fixed pre-existing bug in handling delimiters in templates 2019-01-01 21:51:29 -08:00
mdipierro
ca6014e6e2 fixed another import in pydal.validators 2019-01-01 20:47:48 -08:00
mdipierro
64315dac4a fixed missed variable 2019-01-01 20:42:21 -08:00
mdipierro
fed82e0007 more flexibility in template 2019-01-01 20:37:11 -08:00
mdipierro
174c1fb31d fixed template reader 2019-01-01 20:34:17 -08:00
mdipierro
089013c40e added missing import 2019-01-01 18:39:09 -08:00
mdipierro
5e0f6c19bf fixed print in template 2019-01-01 18:28:24 -08:00
mdipierro
1fff3fc237 fixed get_digest in pydal 2019-01-01 18:10:03 -08:00
mdipierro
32a8a15187 fixed import of validators 2019-01-01 17:39:50 -08:00
mdipierro
b42a3a968f fixed more imports 2019-01-01 17:18:25 -08:00
mdipierro
d3666ee79a fixed scheduler import 2019-01-01 16:54:17 -08:00
mdipierro
cfa0d742fc fixed py3 imports in pydal 2019-01-01 16:32:37 -08:00
mdipierro
b3d92e8c7d ipaddress 2019-01-01 16:24:25 -08:00
mdipierro
e8860233a0 moved scheduler and validator into pydal 2019-01-01 16:01:04 -08:00
mdipierro
019cad9640 modular template 2019-01-01 15:09:06 -08:00
mdipierro
cef31f1277 fixed submodule reference 2018-12-09 19:25:57 -08:00
mdipierro
040966850c fixed submodule reference 2018-12-09 19:08:52 -08:00
mdipierro
79b2685fba fixed autocomplete widget feature, thanks Paolo 2018-12-09 19:02:33 -08:00
mdipierro
a87ee9a966 changed pydal 2018-12-09 18:59:46 -08:00
mdipierro
3ee6ddb081 allow 0 to be an id, thanks Paolo 2018-12-09 18:53:45 -08:00
mdipierro
4b587c45df Merge pull request #2061 from Faelysse/master
[Py3] Open shell executed files in binary mode, fixes #2060
2018-12-05 20:49:48 -08:00
mdipierro
134d9a11c8 Merge pull request #2059 from erbalito/master
resolves #2058
2018-12-05 20:49:16 -08:00
Faelysse
08f04814ba Open shell executed files in binary mode, fixes #2060
Non-ASCII characters in executed files caused a crash, this fixes such
behaviour.
2018-12-03 16:14:05 +01:00
erbalito
178f48799a Merge branch 'master' into master 2018-11-26 11:46:59 -03:00
erbalito
109206e721 Merge pull request #1 from erbalito/erbalito-patch-1
fixes #2058
2018-11-26 11:43:38 -03:00
erbalito
9d8fce0687 fixes #2058
I've modified redis_cache.py to be able to optionally provide an application name to RedisCache constructor. This makes possible to share cache between different web2py applications if needed.
2018-11-21 09:40:19 -03:00
mdipierro
5368a14c43 Merge pull request #2054 from vinyldarkscratch/validators/image_aspect_ratio
Add aspect ratio to IS_IMAGE() validator
2018-11-18 11:09:52 -08:00
mdipierro
f119f1fcb2 Merge pull request #2053 from boriscougar/patch-1
redis_scheduler failing
2018-11-18 11:09:26 -08:00
mdipierro
c52e75624c Merge pull request #2055 from erbalito/patch-1
Update redis_cache.py to allow sharing cache between applications
2018-11-18 11:06:20 -08:00
erbalito
66f6549817 Update redis_cache.py to allow sharing cache between different applications
I'm not sure if this would help someone else. I've always had this scenario where some of my web2py applications share the models, and therefore, need to share the cache (I use Redis). In the past I've asked if there was any possibility to share cache between applications, but I got no answers:
https://groups.google.com/forum/#!searchin/web2py/share$20cache$20application%7Csort:date/web2py/iNCzMq8ADnw/ufyPBWajBQAJ

Anyway, I've noticed that I could make a simple change to redis_cache.py to achieve what I was looking for: I've added the optional argument "application" to RedisCache(). If you inspect the RedisCache() code, you will notice that previously it was forced to use current.request.application for the instance_name. With this simple change I did, it's possible to provide an application name to the RedisCache() constructor.

This has worked like a charm for my specific scenario, and I think I can say that it has backwards compatibility. Still, if this proposal isn't accepted, I would like to hear about some alternative to share cache between applications. Thanks!
2018-11-14 12:47:04 -03:00
Vinyl Darkscratch
62df950f50 Add aspect ratio validation tests 2018-11-03 21:17:13 -08:00
Vinyl Darkscratch
7318d28f1a Add aspect ratio to IS_IMAGE() validator 2018-11-03 21:06:43 -08:00
Boris Aramis Aguilar Rodríguez
a79ad25001 redis_scheduler failing
redis_scheduler failed when calculating next_run_time (method total_seconds() belongs to timedelta)
2018-11-01 15:04:22 -06:00
mdipierro
bf27c8c394 Merge pull request #2048 from timnyborg/patch-3
Allow custom json encoder
2018-11-01 08:24:53 -07:00
mdipierro
bbaf151f25 Merge pull request #2047 from kvanzuijlen/fix_issues_for_python3
'unique_key' shouldn't be saved as bytes
2018-11-01 08:24:27 -07:00
mdipierro
41448926cb Merge pull request #2045 from veiko99/traceback_errors
Fixes python3 traceback errors
2018-11-01 08:23:02 -07:00
mdipierro
2fa6b644ad Merge pull request #2043 from timnyborg/patch-2
fix selectable breaking with custom formstyle
2018-11-01 08:21:42 -07:00
mdipierro
9824ead2e7 Merge pull request #2041 from rif/master
fix recaptcha2 for python3
2018-11-01 08:21:11 -07:00
mdipierro
eefdd6f887 Merge pull request #2040 from d3im/master
fix plugin.html view to work with python3
2018-11-01 08:19:54 -07:00
mdipierro
623bbc4349 Merge pull request #2034 from timnyborg/patch-1
correct rendering of jsonrpcerrors
2018-11-01 08:19:32 -07:00
mdipierro
f307d32288 Merge pull request #2033 from cbenhagen/patch-1
Fix basic_auth with Python 3
2018-11-01 08:19:00 -07:00
mdipierro
eaa34a7f9f Merge pull request #2032 from rayluo/generic-csv
Create generic.csv
2018-11-01 08:18:35 -07:00
Tim Nyborg
77a5c01ac9 Allow custom json encoder
This lets an application use a custom JSONEncoder when making jsonrpc calls (to automatically handle, say, serialization of datetime, or Decimal, or custom classes).

e.g.:
    
import json, datetime, decimal
class ExtendedEncoder(json.JSONEncoder):
    def default(self, obj):
        if isinstance(obj, datetime.datetime):
            return obj.isoformat()
        if isinstance(obj, decimal.Decimal):
            return str(obj)
        return super(ExtendedEncoder, self).default(obj)
    
api = ServerProxy(address, version='2.0', encoder=ExtendedEncoder)
2018-10-29 15:16:28 +00:00
Koen van Zuijlen
ec41d49454 'unique_key' shouldn't be saved as bytes 2018-10-29 13:47:35 +01:00
Koen van Zuijlen
2be479fc9f Merge branch 'master' into fix_issues_for_python3 2018-10-29 13:47:06 +01:00
Veiko Aasa
5887e43248 Fixes #1829 2018-10-20 13:58:41 +03:00
Veiko Aasa
c5e1ddea20 Fixes #1998 2018-10-20 13:54:04 +03:00
Tim Nyborg
aca4947927 fix selectable breaking with custom formstyle
the recently added elif results in a 'function is not iterable' exception if you have a custom formstyle function
2018-10-19 16:36:13 +01:00
Radu Ioan Fericean
7de0a3b53f fix recaptcha2 for python3 2018-10-18 16:27:15 +03:00
d3im
508e96c486 fix plugin.html view to work with python3 2018-10-18 09:20:29 +02:00
Tim Nyborg
5d9f17d414 correct rendering of jsonrpcerrors
Currently, JSONRPCErrors get spit into a console or ticket one line per character (due to the  '\n'.join(data) applying to a string).  This fixes that issue while preserving new lines for arrays.
2018-10-09 10:51:38 +01:00
Ben Hagen
a5d7827fc7 Fix basic_auth with Python 3
`base64.b64decode` returns bytes and thus the separator needs to be a bytes-like object.
2018-10-08 15:47:13 +02:00
Ray Luo
313f7292f8 Create generic.csv 2018-10-07 01:02:50 -07:00
23 changed files with 1129 additions and 6820 deletions

View File

@@ -85,7 +85,7 @@ def deletefile(arglist):
{{=peekfile('models',m)}}
</span>
<span class="extras">
{{if len(defines[m]):}}{{=T("defines tables")}} {{pass}}{{=XML(', '.join([B(table).xml() for table in defines[m]]))}}
{{if len(defines[m]):}}{{=T("defines tables")}} {{pass}}{{=XML(b', '.join([B(table).xml() for table in defines[m]]))}}
</span>
</li>
{{pass}}
@@ -118,7 +118,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
{{=peekfile('controllers',c)}}
</span>
<span class="extras celled">
{{if functions[c]:}}{{=T("exposes")}} {{pass}}{{=XML(', '.join([A(f,_href=URL(a=app,c=c[:-3],f=f)).xml() for f in functions[c]]))}}
{{if functions[c]:}}{{=T("exposes")}} {{pass}}{{=XML(b', '.join([A(f,_href=URL(a=app,c=c[:-3],f=f)).xml() for f in functions[c]]))}}
</span>
</li>
{{pass}}
@@ -145,7 +145,7 @@ for c in controllers: controller_functions+=[c[:-3]+'/%s.html'%x for x in functi
</span>
<span class="extras celled">
{{if c in extend:}}{{=T("extends")}} <b>{{=extend[c]}}</b> {{pass}}
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(', '.join([B(f).xml() for f in include[c]]))}}
{{if include[c]:}}{{=T("includes")}} {{pass}}{{=XML(b', '.join([B(f).xml() for f in include[c]]))}}
</span>
</li>
{{pass}}

View File

@@ -0,0 +1,17 @@
{{"""Usage:
def controller():
return {"": db().select(db.thing.ALL)}
And then visit that controller with a .csv extention name
"""
}}{{if len(response._vars)==1:}}{{
# Not yet find a Python 2/3 compatible StringIO pattern,
# we avoid this solution http://web2py.com/books/default/chapter/29/10/services#CSV
# Here we buffer the entire csv file instead (it is your controller's job to limit the volume anyway),
# based on: http://web2py.com/books/default/chapter/29/06/the-database-abstraction-layer#CSV-one-Table-at-a-time-
content = response._vars[next(iter(response._vars))]
response.headers['Content-Type'] = 'application/vnd.ms-excel'
response.write(str(content), escape=False)
}}{{pass}}
Can't render this file because it contains an unexpected character in line 1 and column 3.

View File

@@ -31,6 +31,7 @@ except ImportError:
"You can also download a complete copy from http://www.web2py.com."
)
from .globals import current
from .html import *
from .validators import *

View File

@@ -1,163 +1,7 @@
import sys
import hashlib
import os
PY2 = sys.version_info[0] == 2
_identity = lambda x: x
from pydal._compat import *
if PY2:
import cPickle as pickle
from cStringIO import StringIO
import copy_reg as copyreg
from HTMLParser import HTMLParser
import urlparse
from htmlentitydefs import entitydefs, name2codepoint
import __builtin__ as builtin
import thread
import Cookie
import urllib2
import Queue
import ConfigParser as configparser
from email.MIMEBase import MIMEBase
from email.Header import Header
from email import Encoders, Charset
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.Charset import add_charset, QP as charset_QP
from urllib import FancyURLopener, urlencode, urlopen
from urllib import quote as urllib_quote, unquote as urllib_unquote, quote_plus as urllib_quote_plus
from string import maketrans
from types import ClassType
import cgi
import cookielib
from xmlrpclib import ProtocolError
from gluon.contrib import ipaddress
BytesIO = StringIO
reduce = reduce
reload = reload
hashlib_md5 = hashlib.md5
iterkeys = lambda d: d.iterkeys()
itervalues = lambda d: d.itervalues()
iteritems = lambda d: d.iteritems()
integer_types = (int, long)
string_types = (str, unicode)
text_type = unicode
basestring = basestring
xrange = xrange
long = long
unichr = unichr
unicodeT = unicode
def implements_bool(cls):
cls.__nonzero__ = cls.__bool__
del cls.__bool__
return cls
def implements_iterator(cls):
cls.next = cls.__next__
del cls.__next__
return cls
def to_bytes(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if isinstance(obj, (bytes, bytearray, buffer)):
return bytes(obj)
if hasattr(obj, 'encode'):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
def to_native(obj, charset='utf8', errors='strict'):
if obj is None or isinstance(obj, str):
return obj
return obj.encode(charset, errors)
else:
import pickle
from io import StringIO, BytesIO
import copyreg
from importlib import reload
from functools import reduce
from html.parser import HTMLParser
from http import cookies as Cookie
from urllib import parse as urlparse
from urllib import request as urllib2
from html.entities import entitydefs, name2codepoint
import builtins as builtin
import _thread as thread
import configparser
import queue as Queue
from email.mime.base import MIMEBase
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email import encoders as Encoders
from email.header import Header
from email.charset import Charset, add_charset, QP as charset_QP
from urllib.request import FancyURLopener, urlopen
from urllib.parse import quote as urllib_quote, unquote as urllib_unquote, urlencode, quote_plus as urllib_quote_plus
from http import cookiejar as cookielib
from xmlrpc.client import ProtocolError
import html # warning, this is the python3 module and not the web2py html module
import ipaddress
hashlib_md5 = lambda s: hashlib.md5(bytes(s, 'utf8'))
iterkeys = lambda d: iter(d.keys())
itervalues = lambda d: iter(d.values())
iteritems = lambda d: iter(d.items())
integer_types = (int,)
string_types = (str,)
text_type = str
basestring = str
xrange = range
long = int
unichr = chr
unicodeT = str
maketrans = str.maketrans
ClassType = type
implements_iterator = _identity
implements_bool = _identity
def to_bytes(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if isinstance(obj, (bytes, bytearray, memoryview)):
return bytes(obj)
if hasattr(obj, 'encode'):
return obj.encode(charset, errors)
raise TypeError('Expected bytes')
def to_native(obj, charset='utf8', errors='strict'):
if obj is None or isinstance(obj, str):
return obj
return obj.decode(charset, errors)
def with_metaclass(meta, *bases):
"""Create a base class with a metaclass."""
# This requires a bit of explanation: the basic idea is to make a dummy
# metaclass for one level of class instantiation that replaces itself with
# the actual metaclass.
class metaclass(meta):
__call__ = type.__call__
__init__ = type.__init__
def __new__(cls, name, this_bases, d):
if this_bases is None:
return type.__new__(cls, name, (), d)
return meta(name, bases, d)
return metaclass('temporary_class', None, {})
def to_unicode(obj, charset='utf-8', errors='strict'):
if obj is None:
return None
if not hasattr(obj, 'decode'):
return text_type(obj)
return obj.decode(charset, errors)
# shortcuts
pjoin = os.path.join
exists = os.path.exists

View File

@@ -22,7 +22,7 @@ logger = logging.getLogger("web2py.cache.redis")
locker = Lock()
def RedisCache(redis_conn=None, debug=False, with_lock=False, fail_gracefully=False, db=None):
def RedisCache(redis_conn=None, debug=False, with_lock=False, fail_gracefully=False, db=None, application=None):
"""
Usage example: put in models::
@@ -47,6 +47,8 @@ def RedisCache(redis_conn=None, debug=False, with_lock=False, fail_gracefully=Fa
When True, only one thread/process can set a value concurrently
fail_gracefully: if redis is unavailable, returns the value computing it
instead of raising an exception
application: if provided, it is used to construct the instance_name,
allowing to share cache between different applications if needed
It can be used pretty much the same as cache.ram()
When you use cache.redis directly you can use :
@@ -83,11 +85,14 @@ def RedisCache(redis_conn=None, debug=False, with_lock=False, fail_gracefully=Fa
locker.acquire()
try:
instance_name = 'redis_instance_' + current.request.application
if not application:
application = current.request.application
instance_name = 'redis_instance_' + application
if not hasattr(RedisCache, instance_name):
setattr(RedisCache, instance_name,
RedisClient(redis_conn=redis_conn, debug=debug,
with_lock=with_lock, fail_gracefully=fail_gracefully))
with_lock=with_lock, fail_gracefully=fail_gracefully,
application=application))
return getattr(RedisCache, instance_name)
finally:
locker.release()
@@ -100,14 +105,15 @@ class RedisClient(object):
RETRIES = 0
def __init__(self, redis_conn=None, debug=False,
with_lock=False, fail_gracefully=False):
with_lock=False, fail_gracefully=False, application=None):
self.request = current.request
self.debug = debug
self.with_lock = with_lock
self.fail_gracefully = fail_gracefully
self.prefix = "w2p:cache:%s:" % self.request.application
self.application = application
self.prefix = "w2p:cache:%s:" % application
if self.request:
app = self.request.application
app = application
else:
app = ''
@@ -120,7 +126,7 @@ class RedisClient(object):
else:
self.storage = self.meta_storage[app]
self.cache_set_key = 'w2p:%s:___cache_set' % self.request.application
self.cache_set_key = 'w2p:%s:___cache_set' % application
self.r_server = redis_conn
self._release_script = register_release_lock(self.r_server)
@@ -276,8 +282,7 @@ class RedisClient(object):
)
stats_collector['w2p_keys'] = dict()
for a in self.r_server.keys("w2p:%s:*" % (
self.request.application)):
for a in self.r_server.keys("w2p:%s:*" % self.application):
stats_collector['w2p_keys']["%s_expire_in_sec" % a] = self.r_server.ttl(a)
return stats_collector

View File

@@ -487,7 +487,7 @@ class RScheduler(Scheduler):
# calc next_run_time based on available slots
# see #1191
next_run_time = task.start_time
secondspassed = self.total_seconds(now - next_run_time)
secondspassed = (now - next_run_time).total_seconds()
steps = secondspassed // task.period + 1
next_run_time += datetime.timedelta(seconds=task.period * steps)

View File

@@ -186,8 +186,8 @@ class MockQuery(object):
if rtn:
if self.unique_key:
# make sure the id and unique_key are correct
if rtn[b'unique_key'] == to_bytes(self.unique_key):
rtn[b'update_record'] = self.update # update record support
if rtn['unique_key'] == self.unique_key:
rtn['update_record'] = self.update # update record support
else:
rtn = None
return [Storage(rtn)] if rtn else []

View File

@@ -34,7 +34,9 @@ import json
class JSONRPCError(RuntimeError):
"Error object for remote procedure call fail"
def __init__(self, code, message, data=''):
def __init__(self, code, message, data=''):
if isinstance(data, basestring):
data = [data]
value = "%s: %s\n%s" % (code, message, '\n'.join(data))
RuntimeError.__init__(self, value)
self.code = code
@@ -82,13 +84,14 @@ class JSONSafeTransport(JSONTransportMixin, SafeTransport):
class ServerProxy(object):
"JSON RPC Simple Client Service Proxy"
def __init__(self, uri, transport=None, encoding=None, verbose=0,version=None):
def __init__(self, uri, transport=None, encoding=None, verbose=0, version=None, json_encoder=None):
self.location = uri # server location (url)
self.trace = verbose # show debug messages
self.exceptions = True # raise errors? (JSONRPCError)
self.timeout = None
self.json_request = self.json_response = ''
self.version = version # '2.0' for jsonrpc2
self.json_encoder = json_encoder # Allow for a custom JSON encoding class
type, uri = urllib.splittype(uri)
if type not in ("http", "https"):
@@ -116,7 +119,7 @@ class ServerProxy(object):
data = {'id': request_id, 'method': method, 'params': args or vars, }
if self.version:
data['jsonrpc'] = self.version #mandatory key/value for jsonrpc2 validation else err -32600
request = json.dumps(data)
request = json.dumps(data, cls=self.json_encoder)
# make HTTP request (retry if connection is lost)
response = self.__transport.request(

View File

@@ -1051,7 +1051,7 @@ class Session(Storage):
if record_id.isdigit() and long(record_id) > 0:
new_unique_key = web2py_uuid()
row = table(record_id)
if row and row[b'unique_key'] == to_bytes(unique_key):
if row and row['unique_key'] == unique_key:
table._db(table.id == record_id).update(unique_key=new_unique_key)
else:
record_id = None

View File

@@ -0,0 +1 @@

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -37,7 +37,7 @@ logger = logging.getLogger("web2py")
if not PY2:
def execfile(filename, global_vars=None, local_vars=None):
with open(filename) as f:
with open(filename, "rb") as f:
code = compile(f.read(), filename, 'exec')
exec(code, global_vars, local_vars)
raw_input = input

View File

@@ -802,8 +802,8 @@ class AutocompleteWidget(object):
$('#%(key3)s').val('');
var e=e_.which?e_.which:e_.keyCode;
function %(u)s(){
$('#%(id)s').val($('#%(key)s :selected').text());
$('#%(key3)s').val($('#%(key)s').val())
$('#%(id)s').val($('#%(key)s option:selected').text());
$('#%(key3)s').val($('#%(key)s option:selected').val())
};
if(e==39) %(u)s();
else if(e==40) {
@@ -824,7 +824,7 @@ class AutocompleteWidget(object):
$('#%(id)s').next('.error').hide();
$('#%(div_id)s').html(data).show().focus();
$('#%(div_id)s select').css('width',$('#%(id)s').css('width'));
$('#%(key3)s').val($('#%(key)s').val());
$('#%(key3)s').val($('#%(key)s option:selected').val());
$('#%(key)s').change(%(u)s).click(%(u)s);
};
});
@@ -855,7 +855,7 @@ class AutocompleteWidget(object):
function doit(e_) {
var e=e_.which?e_.which:e_.keyCode;
function %(u)s(){
$('#%(id)s').val($('#%(key)s').val())
$('#%(id)s').val($('#%(key)s option:selected').val())
};
if(e==39) %(u)s();
else if(e==40) {
@@ -1912,23 +1912,10 @@ class SQLFORM(FORM):
fields[fieldname] = [safe_int(
x) for x in (value and [value] or [])]
elif field.type == 'integer':
if value is not None:
fields[fieldname] = safe_int(value)
fields[fieldname] = safe_int(value, None)
elif field.type.startswith('reference'):
## Avoid "constraint violation" exception when you have a
## optional reference field without the dropdown in form. I.e.,
## a field with code to be typed, in a data-entry form.
##
## When your reference field doesn't have IS_EMPTY_OR()
## validator, "value" will come here as a string. So,
## safe_int() will return 0. In this case, insert will raise
## the constraint violation because there's no id=0 in
## referenced table.
if isinstance(self.table, Table) and not keyed:
if not value:
fields[fieldname] = None
else:
fields[fieldname] = safe_int(value)
fields[fieldname] = safe_int(value, None)
elif field.type == 'double':
if value is not None:
fields[fieldname] = safe_float(value)
@@ -3093,7 +3080,7 @@ class SQLFORM(FORM):
if formstyle == 'bootstrap':
# add space between buttons
htmltable = FORM(htmltable, DIV(_class='form-actions', *inputs))
elif 'bootstrap' in formstyle : # Same for bootstrap 3 & 4
elif not callable(formstyle) and 'bootstrap' in formstyle: # Same for bootstrap 3 & 4
htmltable = FORM(htmltable, DIV(_class='form-group web2py_table_selectable_actions', *inputs))
else:
htmltable = FORM(htmltable, *inputs)

View File

@@ -1,938 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
| 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.
Templating syntax
------------------
"""
import os
import cgi
import logging
from re import compile, sub, escape, DOTALL
from gluon._compat import StringIO, unicodeT, to_unicode, to_bytes, to_native, basestring
try:
# have web2py
from gluon.restricted import RestrictedError
from gluon.globals import current
except ImportError:
# do not have web2py
current = None
def RestrictedError(a, b, c):
logging.error(str(a) + ':' + str(b) + ':' + str(c))
return RuntimeError
class Node(object):
"""
Basic Container Object
"""
def __init__(self, value=None, pre_extend=False):
self.value = value
self.pre_extend = pre_extend
def __str__(self):
return str(self.value)
class SuperNode(Node):
def __init__(self, name='', pre_extend=False):
self.name = name
self.value = None
self.pre_extend = pre_extend
def __str__(self):
if self.value:
return str(self.value)
else:
# raise SyntaxError("Undefined parent block ``%s``. \n" % self.name + "You must define a block before referencing it.\nMake sure you have not left out an ``{{end}}`` tag." )
return ''
def __repr__(self):
return "%s->%s" % (self.name, self.value)
def output_aux(node, blocks):
# If we have a block level
# If we can override this block.
# Override block from vars.
# Else we take the default
# Else its just a string
return (blocks[node.name].output(blocks)
if node.name in blocks else
node.output(blocks)) \
if isinstance(node, BlockNode) \
else str(node)
class BlockNode(Node):
"""
Block Container.
This Node can contain other Nodes and will render in a hierarchical order
of when nodes were added.
ie::
{{ block test }}
This is default block test
{{ end }}
"""
def __init__(self, name='', pre_extend=False, delimiters=('{{', '}}')):
"""
name - Name of this Node.
"""
self.nodes = []
self.name = name
self.pre_extend = pre_extend
self.left, self.right = delimiters
def __repr__(self):
lines = ['%sblock %s%s' % (self.left, self.name, self.right)]
lines += [str(node) for node in self.nodes]
lines.append('%send%s' % (self.left, self.right))
return ''.join(lines)
def __str__(self):
"""
Get this BlockNodes content, not including child Nodes
"""
return ''.join(str(node) for node in self.nodes
if not isinstance(node, BlockNode))
def append(self, node):
"""
Adds an element to the nodes.
Args:
node: Node object or string to append.
"""
if isinstance(node, str) or isinstance(node, Node):
self.nodes.append(node)
else:
raise TypeError("Invalid type; must be instance of ``str`` or ``BlockNode``. %s" % node)
def extend(self, other):
"""
Extends the list of nodes with another BlockNode class.
Args:
other: BlockNode or Content object to extend from.
"""
if isinstance(other, BlockNode):
self.nodes.extend(other.nodes)
else:
raise TypeError(
"Invalid type; must be instance of ``BlockNode``. %s" % other)
def output(self, blocks):
"""
Merges all nodes into a single string.
Args:
blocks: Dictionary of blocks that are extending from this template.
"""
return ''.join(output_aux(node, blocks) for node in self.nodes)
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):
self.name = name
self.nodes = []
self.blocks = {}
self.pre_extend = pre_extend
def __str__(self):
return ''.join(output_aux(node, self.blocks) for node in self.nodes)
def _insert(self, other, index=0):
"""
Inserts object at index.
"""
if isinstance(other, (str, Node)):
self.nodes.insert(index, other)
else:
raise TypeError(
"Invalid type, must be instance of ``str`` or ``Node``.")
def insert(self, other, index=0):
"""
Inserts object at index.
You may pass a list of objects and have them inserted.
"""
if isinstance(other, (list, tuple)):
# Must reverse so the order stays the same.
other.reverse()
for item in other:
self._insert(item, index)
else:
self._insert(other, index)
def append(self, node):
"""
Adds a node to list. If it is a BlockNode then we assign a block for it.
"""
if isinstance(node, (str, Node)):
self.nodes.append(node)
if isinstance(node, BlockNode):
self.blocks[node.name] = node
else:
raise TypeError("Invalid type, must be instance of ``str`` or ``BlockNode``. %s" % node)
def extend(self, other):
"""
Extends the objects list of nodes with another objects nodes
"""
if isinstance(other, BlockNode):
self.nodes.extend(other.nodes)
self.blocks.update(other.blocks)
else:
raise TypeError(
"Invalid type; must be instance of ``BlockNode``. %s" % other)
def clear_content(self):
self.nodes = []
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)
r_multiline = compile(r'(""".*?""")|(\'\'\'.*?\'\'\')', DOTALL)
# These are used for re-indentation.
# Indent + 1
re_block = compile('^(elif |else:|except:|except |finally:).*$', DOTALL)
# Indent - 1
re_unblock = compile('^(return|continue|break|raise)( .*)?$', DOTALL)
# Indent - 1
re_pass = compile('^pass( .*)?$', DOTALL)
def __init__(self, text,
name="ParserContainer",
context=dict(),
path='views/',
writer='response.write',
lexers={},
delimiters=('{{', '}}'),
_super_nodes = [],
):
# Keep a root level name.
self.name = name
# Raw text to start parsing.
self.text = text
# Writer to use (refer to the default for an example).
# This will end up as
# "%s(%s, escape=False)" % (self.writer, value)
self.writer = writer
# Dictionary of custom name lexers to use.
if isinstance(lexers, dict):
self.lexers = lexers
else:
self.lexers = {}
# Path of templates
self.path = path
# Context for templates.
self.context = context
# allow optional alternative delimiters
if delimiters != self.default_delimiters:
escaped_delimiters = (escape(delimiters[0]),
escape(delimiters[1]))
self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters, DOTALL)
elif hasattr(context.get('response', None), 'delimiters'):
if (context['response'].delimiters != self.default_delimiters) and (context['response'].delimiters != None):
delimiters = context['response'].delimiters
escaped_delimiters = (
escape(delimiters[0]),
escape(delimiters[1]))
self.r_tag = compile(r'(%s.*?%s)' % escaped_delimiters,
DOTALL)
self.delimiters = delimiters
# Create a root level Content that everything will go into.
self.content = Content(name=name)
# Stack will hold our current stack of nodes.
# As we descend into a node, it will be added to the stack
# And when we leave, it will be removed from the stack.
# self.content should stay on the stack at all times.
self.stack = [self.content]
# This variable will hold a reference to every super block
# that we come across in this template.
self.super_nodes = []
# This variable will hold a reference to the child
# super nodes that need handling.
self.child_super_nodes = _super_nodes
# This variable will hold a reference to every block
# that we come across in this template
self.blocks = {}
# Begin parsing.
self.parse(text)
def to_string(self):
"""
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):
"Makes sure str works exactly the same as python 3"
return self.to_string()
def __unicode__(self):
"Makes sure str works exactly the same as python 3"
return self.to_string()
def reindent(self, text):
"""
Reindents a string of unindented python code.
"""
# Get each of our lines into an array.
lines = text.split('\n')
# Our new lines
new_lines = []
# Keeps track of how many indents we have.
# Used for when we need to drop a level of indentation
# only to reindent on the next line.
credit = 0
# Current indentation
k = 0
#################
# THINGS TO KNOW
#################
# k += 1 means indent
# k -= 1 means unindent
# credit = 1 means unindent on the next line.
for raw_line in lines:
line = raw_line.strip()
# ignore empty lines
if not line:
continue
# If we have a line that contains python code that
# should be unindented for this line of code.
# and then reindented for the next line.
if TemplateParser.re_block.match(line):
k = k + credit - 1
# We obviously can't have a negative indentation
k = max(k, 0)
# Add the indentation!
new_lines.append(' ' * (4 * k) + line)
# Bank account back to 0 again :(
credit = 0
# If we are a pass block, we obviously de-dent.
if TemplateParser.re_pass.match(line):
k -= 1
# If we are any of the following, de-dent.
# However, we should stay on the same level
# But the line right after us will be de-dented.
# So we add one credit to keep us at the level
# while moving back one indentation level.
if TemplateParser.re_unblock.match(line):
credit = 1
k -= 1
# If we are an if statement, a try, or a semi-colon we
# probably need to indent the next line.
if line.endswith(':') and not line.startswith('#'):
k += 1
# This must come before so that we can raise an error with the
# right content.
new_text = '\n'.join(new_lines)
if k > 0:
self._raise_error('missing "pass" in view', new_text)
elif k < 0:
self._raise_error('too many "pass" in view', new_text)
return new_text
def _raise_error(self, message='', text=None):
"""
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):
"""
Attempts to open ``filename`` and retrieve its text.
This will use self.path to search for the file.
"""
# If they didn't specify a filename, how can we find one!
if not filename.strip():
self._raise_error('Invalid template filename')
# Allow Views to include other views dynamically
context = self.context
if current and "response" not in context:
context["response"] = getattr(current, 'response', None)
# Get the filename; filename looks like ``"template.html"``.
# We need to eval to remove the quotes and get the string type.
filename = eval(filename, context)
# Allow empty filename for conditional extend and include directives.
if not filename:
return ''
# Get the path of the file on the system.
filepath = self.path and os.path.join(self.path, filename) or filename
# try to read the text.
try:
fileobj = open(filepath, 'rb')
text = fileobj.read()
fileobj.close()
except IOError:
self._raise_error('Unable to open included view file: ' + filepath)
text = to_native(text)
return text
def include(self, content, filename):
"""
Includes ``filename`` here.
"""
text = self._get_file_text(filename)
t = TemplateParser(text,
name=filename,
context=self.context,
path=self.path,
writer=self.writer,
delimiters=self.delimiters)
content.append(t.content)
def extend(self, filename):
"""
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)
# Create out nodes list to send to the parent
super_nodes = []
# We want to include any non-handled nodes.
super_nodes.extend(self.child_super_nodes)
# And our nodes as well.
super_nodes.extend(self.super_nodes)
t = TemplateParser(text,
name=filename,
context=self.context,
path=self.path,
writer=self.writer,
delimiters=self.delimiters,
_super_nodes=super_nodes)
# Make a temporary buffer that is unique for parent
# template.
buf = BlockNode(
name='__include__' + filename, delimiters=self.delimiters)
pre = []
# Iterate through each of our nodes
for node in self.content.nodes:
# If a node is a block
if isinstance(node, BlockNode):
# That happens to be in the parent template
if node.name in t.content.blocks:
# Do not include it
continue
if isinstance(node, Node):
# Or if the node was before the extension
# we should not include it
if node.pre_extend:
pre.append(node)
continue
# Otherwise, it should go int the
# Parent templates {{include}} section.
buf.append(node)
else:
buf.append(node)
# Clear our current nodes. We will be replacing this with
# the parent nodes.
self.content.nodes = []
t_content = t.content
# Set our include, unique by filename
t_content.blocks['__include__' + filename] = buf
# Make sure our pre_extended nodes go first
t_content.insert(pre)
# Then we extend our blocks
t_content.extend(self.content)
# Work off the parent node.
self.content = t_content
def parse(self, text):
# Basically, r_tag.split will split the text into
# an array containing, 'non-tag', 'tag', 'non-tag', 'tag'
# so if we alternate this variable, we know
# what to look for. This is alternate to
# line.startswith("{{")
in_tag = False
extend = None
pre_extend = True
# Use a list to store everything in
# This is because later the code will "look ahead"
# for missing strings or brackets.
ij = self.r_tag.split(text)
# j = current index
# i = current item
stack = self.stack
for j in range(len(ij)):
i = ij[j]
if i:
if not stack:
self._raise_error('The "end" tag is unmatched, please check if you have a starting "block" tag')
# Our current element in the stack.
top = stack[-1]
if in_tag:
line = i
# Get rid of delimiters
line = line[len(self.delimiters[0]): \
-len(self.delimiters[1])].strip()
# This is bad juju, but let's do it anyway
if not line:
continue
# We do not want to replace the newlines in code,
# only in block comments.
def remove_newline(re_val):
# Take the entire match and replace newlines with
# escaped newlines.
return re_val.group(0).replace('\n', '\\n')
# Perform block comment escaping.
# This performs escaping ON anything
# in between """ and """
line = sub(TemplateParser.r_multiline,
remove_newline,
line)
if line.startswith('='):
# IE: {{=response.title}}
name, value = '=', line[1:].strip()
else:
v = line.split(' ', 1)
if len(v) == 1:
# Example
# {{ include }}
# {{ end }}
name = v[0]
value = ''
else:
# Example
# {{ block pie }}
# {{ include "layout.html" }}
# {{ for i in range(10): }}
name = v[0]
value = v[1]
# This will replace newlines in block comments
# with the newline character. This is so that they
# retain their formatting, but squish down to one
# line in the rendered template.
# First check if we have any custom lexers
if name in self.lexers:
# Pass the information to the lexer
# and allow it to inject in the environment
# You can define custom names such as
# '{{<<variable}}' which could potentially
# write unescaped version of the variable.
self.lexers[name](parser=self,
value=value,
top=top,
stack=stack)
elif name == '=':
# So we have a variable to insert into
# the template
buf = "\n%s(%s)" % (self.writer, value)
top.append(Node(buf, pre_extend=pre_extend))
elif name == 'block' and not value.startswith('='):
# Make a new node with name.
node = BlockNode(name=value.strip(),
pre_extend=pre_extend,
delimiters=self.delimiters)
# Append this node to our active node
top.append(node)
# Make sure to add the node to the stack.
# so anything after this gets added
# to this node. This allows us to
# "nest" nodes.
stack.append(node)
elif name == 'end' and not value.startswith('='):
# We are done with this node.
# Save an instance of it
self.blocks[top.name] = top
# Pop it.
stack.pop()
elif name == 'super' and not value.startswith('='):
# Get our correct target name
# If they just called {{super}} without a name
# attempt to assume the top blocks name.
if value:
target_node = value
else:
target_node = top.name
# Create a SuperNode instance
node = SuperNode(name=target_node,
pre_extend=pre_extend)
# Add this to our list to be taken care of
self.super_nodes.append(node)
# And put in in the tree
top.append(node)
elif name == 'include' and not value.startswith('='):
# If we know the target file to include
if value:
self.include(top, value)
# Otherwise, make a temporary include node
# That the child node will know to hook into.
else:
include_node = BlockNode(
name='__include__' + self.name,
pre_extend=pre_extend,
delimiters=self.delimiters)
top.append(include_node)
elif name == 'extend' and not value.startswith('='):
# We need to extend the following
# template.
extend = value
pre_extend = False
else:
# If we don't know where it belongs
# we just add it anyways without formatting.
if line and in_tag:
# Split on the newlines >.<
tokens = line.split('\n')
# We need to look for any instances of
# for i in range(10):
# = i
# pass
# So we can properly put a response.write() in place.
continuation = False
len_parsed = 0
for k, token in enumerate(tokens):
token = tokens[k] = token.strip()
len_parsed += len(token)
if token.startswith('='):
if token.endswith('\\'):
continuation = True
tokens[k] = "\n%s(%s" % (
self.writer, token[1:].strip())
else:
tokens[k] = "\n%s(%s)" % (
self.writer, token[1:].strip())
elif continuation:
tokens[k] += ')'
continuation = False
buf = "\n%s" % '\n'.join(tokens)
top.append(Node(buf, pre_extend=pre_extend))
else:
# It is HTML so just include it.
buf = "\n%s(%r, escape=False)" % (self.writer, i)
top.append(Node(buf, pre_extend=pre_extend))
# Remember: tag, not tag, tag, not tag
in_tag = not in_tag
# Make a list of items to remove from child
to_rm = []
# Go through each of the children nodes
for node in self.child_super_nodes:
# If we declared a block that this node wants to include
if node.name in self.blocks:
# Go ahead and include it!
node.value = self.blocks[node.name]
# Since we processed this child, we don't need to
# pass it along to the parent
to_rm.append(node)
# Remove some of the processed nodes
for node in to_rm:
# Since this is a pointer, it works beautifully.
# Sometimes I miss C-Style pointers... I want my asterisk...
self.child_super_nodes.remove(node)
# If we need to extend a template.
if extend:
self.extend(extend)
# We need this for integration with gluon
def parse_template(filename,
path='views/',
context=dict(),
lexers={},
delimiters=('{{', '}}')
):
"""
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
if isinstance(filename, basestring):
fname = os.path.join(path, filename)
try:
with open(fname, 'rb') as fp:
text = fp.read()
except IOError:
raise RestrictedError(filename, '', 'Unable to find the file')
else:
text = filename.read()
text = to_native(text)
# Use the file contents to get a parsed template and return it.
return str(TemplateParser(text, context=context, path=path, lexers=lexers, delimiters=delimiters))
def get_parsed(text):
"""
Returns the indented python code of text. Useful for unit testing.
"""
return str(TemplateParser(text))
class DummyResponse():
def __init__(self):
self.body = StringIO()
def write(self, data, escape=True):
if not escape:
self.body.write(str(data))
elif hasattr(data, 'xml') and callable(data.xml):
self.body.write(data.xml())
else:
# make it a string
if not isinstance(data, (str, unicodeT)):
data = str(data)
elif isinstance(data, unicodeT):
data = data.encode('utf8', 'xmlcharrefreplace')
data = cgi.escape(data, True).replace("'", "&#x27;")
self.body.write(data)
class NOESCAPE():
"""
A little helper to avoid escaping.
"""
def __init__(self, text):
self.text = text
def xml(self):
return self.text
# And this is a generic render function.
# Here for integration with gluon.
def render(content="hello world",
stream=None,
filename=None,
path=None,
context={},
lexers={},
delimiters=('{{', '}}'),
writer='response.write'
):
"""
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:
from gluon.globals import Response
except ImportError:
# Working standalone. Build a mock Response object.
Response = DummyResponse
# Add it to the context so we can use it.
if 'NOESCAPE' not in context:
context['NOESCAPE'] = NOESCAPE
if isinstance(content, unicodeT):
content = content.encode('utf8')
# save current response class
if context and 'response' in context:
old_response_body = context['response'].body
context['response'].body = StringIO()
else:
old_response_body = None
context['response'] = Response()
# If we don't have anything to render, why bother?
if not content and not stream and not filename:
raise SyntaxError("Must specify a stream or filename or content")
# Here for legacy purposes, probably can be reduced to
# something more simple.
close_stream = False
if not stream:
if filename:
stream = open(filename, 'rb')
close_stream = True
elif content:
stream = StringIO(to_native(content))
# Execute the template.
code = str(TemplateParser(stream.read(
), context=context, path=path, lexers=lexers, delimiters=delimiters, writer=writer))
try:
exec(code, context)
except Exception:
# for i,line in enumerate(code.split('\n')): print i,line
raise
if close_stream:
stream.close()
# Returned the rendered content.
text = context['response'].body.getvalue()
if old_response_body is not None:
context['response'].body = old_response_body
return text

1
gluon/template.py Symbolic link
View File

@@ -0,0 +1 @@
packages/template/template.py

View File

@@ -11,7 +11,7 @@ class TestCron(unittest.TestCase):
def test_Token(self):
appname_path = os.path.join(os.getcwd(), 'applications', 'welcome')
t = Token(path=appname_path)
self.assertNotEqual(t.acquire(), None)
self.assertNotEqual(t.acquire(startup=True), None)
self.assertFalse(t.release())
self.assertEqual(t.acquire(), None)
self.assertTrue(t.release())

View File

@@ -24,11 +24,11 @@ class TestTemplate(unittest.TestCase):
self.assertEqual(render(content='"abc"'), '"abc"')
self.assertEqual(render(content='"a\'bc"'), '"a\'bc"')
self.assertEqual(render(content='"a\"bc"'), '"a\"bc"')
self.assertEqual(render(content=r'''"a\"bc"'''), r'"a\"bc"')
self.assertEqual(render(content=r'''"""abc\""""'''), r'"""abc\""""')
self.assertEqual(render(content=r'"a\"bc"'), r'"a\"bc"')
self.assertEqual(render(content=r'"""abc\""""'), r'"""abc\""""')
def testEqualWrite(self):
"test generation of response.write from ="
"test generation of response.write"
self.assertEqual(render(content='{{=2+2}}'), '4')
self.assertEqual(render(content='{{="abc"}}'), 'abc')
# whitespace is stripped
@@ -81,55 +81,55 @@ class TestTemplate(unittest.TestCase):
else:
setattr(module, fn_name, unpatch)
def dummy_open(path, mode):
def dummy_open(path):
if path == pjoin('views', 'layout.html'):
return StringIO("{{block left_sidebar}}left{{end}}"
"{{include}}"
"{{block right_sidebar}}right{{end}}")
return ("{{block left_sidebar}}left{{end}}"
"{{include}}"
"{{block right_sidebar}}right{{end}}")
elif path == pjoin('views', 'layoutbrackets.html'):
return StringIO("[[block left_sidebar]]left[[end]]"
"[[include]]"
"[[block right_sidebar]]right[[end]]")
return ("[[block left_sidebar]]left[[end]]"
"[[include]]"
"[[block right_sidebar]]right[[end]]")
elif path == pjoin('views', 'default', 'index.html'):
return StringIO("{{extend 'layout.html'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
return ("{{extend 'layout.html'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
elif path == pjoin('views', 'default', 'indexbrackets.html'):
return StringIO("[[extend 'layoutbrackets.html']]"
"[[block left_sidebar]][[super]] [[end]]"
"to"
"[[block right_sidebar]] [[super]][[end]]")
return ("[[extend 'layoutbrackets.html']]"
"[[block left_sidebar]][[super]] [[end]]"
"to"
"[[block right_sidebar]] [[super]][[end]]")
elif path == pjoin('views', 'default', 'missing.html'):
return StringIO("{{extend 'wut'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
return ("{{extend 'wut'}}"
"{{block left_sidebar}}{{super}} {{end}}"
"to"
"{{block right_sidebar}} {{super}}{{end}}")
elif path == pjoin('views', 'default', 'noescape.html'):
return StringIO("""{{=NOESCAPE('<script></script>')}}""")
return "{{=NOESCAPE('<script></script>')}}"
raise IOError
with monkey_patch(template, 'open', dummy_open):
self.assertEqual(
render(filename=pjoin('views', 'default', 'index.html'),
path='views'),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', delimiters=('[[', ']]')),
'left to right')
self.assertRaises(
RestrictedError,
render,
filename=pjoin('views', 'default', 'missing.html'),
path='views')
response = template.DummyResponse()
response.delimiters = ('[[', ']]')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', context={'response': response}),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'noescape.html'),
context={'NOESCAPE': template.NOESCAPE}),
'<script></script>')
self.assertEqual(
render(filename=pjoin('views', 'default', 'index.html'),
path='views', reader=dummy_open),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', delimiters='[[ ]]', reader=dummy_open),
'left to right')
self.assertRaises(
RestrictedError,
render,
filename=pjoin('views', 'default', 'missing.html'),
path='views',
reader=dummy_open)
response = template.DummyResponse()
response.delimiters = ('[[', ']]')
self.assertEqual(
render(filename=pjoin('views', 'default', 'indexbrackets.html'),
path='views', context={'response': response}, reader=dummy_open),
'left to right')
self.assertEqual(
render(filename=pjoin('views', 'default', 'noescape.html'),
context={'NOESCAPE': template.NOESCAPE}, reader=dummy_open),
'<script></script>')

View File

@@ -193,7 +193,7 @@ class TestMail(unittest.TestCase):
message = TestMail.DummySMTP.inbox.pop()
attachment = message.parsed_payload.get_payload(1).get_payload(decode=True)
with open(module_file, 'rb') as mf:
self.assertEqual(attachment.decode('utf-8'), mf.read().decode('utf-8'))
self.assertEqual(attachment, mf.read())
# Test missing attachment name error
stream = open(module_file)
self.assertRaises(Exception, lambda *args, **kwargs: Mail.Attachment(*args, **kwargs), stream)

View File

@@ -977,6 +977,8 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, (img, 'oops'))
rtn = IS_IMAGE(error_message='oops', minsize=(100, 50))(img)
self.assertEqual(rtn, (img, 'oops'))
rtn = IS_IMAGE(error_message='oops', aspectratio=(1, 1))(img)
self.assertEqual(rtn, (img, 'oops'))
img = DummyImageFile('test', 'gif', 50, 100)
rtn = IS_IMAGE()(img)
@@ -985,6 +987,8 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, (img, 'oops'))
rtn = IS_IMAGE(error_message='oops', minsize=(100, 50))(img)
self.assertEqual(rtn, (img, 'oops'))
rtn = IS_IMAGE(error_message='oops', aspectratio=(1, 1))(img)
self.assertEqual(rtn, (img, 'oops'))
img = DummyImageFile('test', 'jpeg', 50, 100)
rtn = IS_IMAGE()(img)
@@ -993,6 +997,8 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, (img, 'oops'))
rtn = IS_IMAGE(error_message='oops', minsize=(100, 50))(img)
self.assertEqual(rtn, (img, 'oops'))
rtn = IS_IMAGE(error_message='oops', aspectratio=(1, 1))(img)
self.assertEqual(rtn, (img, 'oops'))
img = DummyImageFile('test', 'png', 50, 100)
rtn = IS_IMAGE()(img)
@@ -1001,6 +1007,8 @@ class TestValidators(unittest.TestCase):
self.assertEqual(rtn, (img, 'oops'))
rtn = IS_IMAGE(error_message='oops', minsize=(100, 50))(img)
self.assertEqual(rtn, (img, 'oops'))
rtn = IS_IMAGE(error_message='oops', aspectratio=(1, 1))(img)
self.assertEqual(rtn, (img, 'oops'))
img = DummyImageFile('test', 'xls', 50, 100)
rtn = IS_IMAGE(error_message='oops')(img)

View File

@@ -905,14 +905,14 @@ class Recaptcha2(DIV):
})
request = urllib2.Request(
url=self.VERIFY_SERVER,
data=params,
data=to_bytes(params),
headers={'Content-type': 'application/x-www-form-urlencoded',
'User-agent': 'reCAPTCHA Python'})
httpresp = urllib2.urlopen(request)
content = httpresp.read()
httpresp.close()
try:
response_dict = json.loads(content)
response_dict = json.loads(to_native(content))
except:
self.errors['captcha'] = self.error_message
return False
@@ -2252,7 +2252,7 @@ class Auth(AuthAPI):
if basic_auth_realm:
raise http_401
return (True, False, False)
(username, sep, password) = base64.b64decode(basic[6:]).partition(':')
(username, sep, password) = base64.b64decode(basic[6:]).partition(b':')
is_valid_user = sep and self.login_bare(username, password)
if not is_valid_user and basic_auth_realm:
raise http_401

File diff suppressed because it is too large Load Diff

View File

@@ -1288,17 +1288,18 @@ end tell
line = py2exe_getline(filename, lineno, *args, **kwargs)
if not line:
try:
f = open(filename, "r")
f = open(filename, "rb")
try:
for i, line in enumerate(f):
line = line.decode('utf-8')
if lineno == i + 1:
break
else:
line = None
line = ''
finally:
f.close()
except (IOError, OSError):
line = None
line = ''
return line
linecache.getline = getline