mirror of
https://github.com/dkmstr/openuds.git
synced 2024-12-22 13:34:04 +03:00
* Cleaning up things
* Using pylint to try to find potentials errors
This commit is contained in:
parent
6dbe561512
commit
3562e4ca56
@ -37,29 +37,36 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import ugettext as _, activate
|
||||
from django.conf import settings
|
||||
from handlers import Handler, HandlerError, AccessDenied, NotFound, RequestError, ResponseError
|
||||
from uds.REST.handlers import Handler, HandlerError, AccessDenied, NotFound, RequestError, ResponseError
|
||||
|
||||
import time
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__all__ = [str(v) for v in ['Handler', 'Dispatcher']]
|
||||
__all__ = [str(v) for v in ['Handler', 'Dispatcher']]
|
||||
|
||||
AUTH_TOKEN_HEADER = 'X-Auth-Token'
|
||||
|
||||
|
||||
class Dispatcher(View):
|
||||
'''
|
||||
This class is responsible of dispatching REST requests
|
||||
'''
|
||||
# This attribute will contain all paths-->handler relations, added at Initialized method
|
||||
services = {'': None} # Will include a default /rest handler, but rigth now this will be fine
|
||||
|
||||
@method_decorator(csrf_exempt)
|
||||
def dispatch(self, request, **kwargs):
|
||||
'''
|
||||
Processes the REST request and routes it wherever it needs to be routed
|
||||
'''
|
||||
logger.debug('Language in dispatcher: {0}'.format(request.LANGUAGE_CODE))
|
||||
import processors
|
||||
from uds.REST import processors
|
||||
|
||||
# Remove session, so response middelwares do nothing with this
|
||||
# Remove session, so response middleware do nothing with this
|
||||
del request.session
|
||||
# Now we extract method and posible variables from path
|
||||
# Now we extract method and possible variables from path
|
||||
path = kwargs['arguments'].split('/')
|
||||
del kwargs['arguments']
|
||||
|
||||
@ -67,22 +74,23 @@ class Dispatcher(View):
|
||||
service = Dispatcher.services
|
||||
full_path = []
|
||||
# Last element will be
|
||||
do_break = False
|
||||
cls = None
|
||||
while len(path) > 0 and not do_break:
|
||||
# .json, .xml, ... will break path recursion
|
||||
do_break = path[0].find('.') != -1
|
||||
while len(path) > 0:
|
||||
clean_path = path[0].split('.')[0]
|
||||
if service.has_key(clean_path):
|
||||
if clean_path in service:
|
||||
service = service[clean_path]
|
||||
full_path.append(path[0])
|
||||
path = path[1:]
|
||||
else:
|
||||
break
|
||||
# .json, .xml, ... will break path recursion
|
||||
if path[0].find('.') != -1:
|
||||
break
|
||||
|
||||
full_path = '/'.join(full_path)
|
||||
logger.debug(full_path)
|
||||
|
||||
# Here, service points to the path
|
||||
cls = service['']
|
||||
if cls is None:
|
||||
return http.HttpResponseNotFound('method not found')
|
||||
@ -91,8 +99,7 @@ class Dispatcher(View):
|
||||
try:
|
||||
p = full_path.split('.')
|
||||
processor = processors.available_processors_ext_dict[p[1]](request)
|
||||
except:
|
||||
# TODO: Extract processor from accept and/or content type?
|
||||
except Exception:
|
||||
processor = processors.available_processors_mime_dict.get(request.META.get('CONTENT_TYPE', 'json'), processors.default_processor)(request)
|
||||
|
||||
# Obtain method to be invoked
|
||||
@ -115,7 +122,7 @@ class Dispatcher(View):
|
||||
return http.HttpResponseNotAllowed(allowedMethods)
|
||||
except AccessDenied:
|
||||
return http.HttpResponseForbidden('access denied')
|
||||
except:
|
||||
except Exception:
|
||||
logger.exception('error accessing attribute')
|
||||
logger.debug('Getting attribute {0} for {1}'.format(http_method, full_path))
|
||||
return http.HttpResponseServerError('Unexcepected error')
|
||||
@ -130,8 +137,8 @@ class Dispatcher(View):
|
||||
start = time.time()
|
||||
response = processor.getResponse(response)
|
||||
logger.debug('Execution time for encoding: {0}'.format(time.time() - start))
|
||||
for k, v in handler.headers().iteritems():
|
||||
response[k] = v
|
||||
for k, val in handler.headers().iteritems():
|
||||
response[k] = val
|
||||
return response
|
||||
except RequestError as e:
|
||||
return http.HttpResponseServerError(unicode(e))
|
||||
@ -149,6 +156,9 @@ class Dispatcher(View):
|
||||
|
||||
@staticmethod
|
||||
def registerSubclasses(classes):
|
||||
'''
|
||||
Try to register Handler subclasses that have not been inherited
|
||||
'''
|
||||
for cls in classes:
|
||||
if len(cls.__subclasses__()) == 0: # Only classes that has not been inherited will be registered as Handlers
|
||||
logger.debug('Found class {0}'.format(cls))
|
||||
|
@ -33,7 +33,6 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.sessions.backends.db import SessionStore
|
||||
from django.conf import settings
|
||||
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
|
||||
@ -45,26 +44,44 @@ AUTH_TOKEN_HEADER = 'HTTP_X_AUTH_TOKEN'
|
||||
|
||||
|
||||
class HandlerError(Exception):
|
||||
'''
|
||||
Generic error for a REST handler
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class NotFound(HandlerError):
|
||||
'''
|
||||
Item not found error
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class AccessDenied(HandlerError):
|
||||
'''
|
||||
Access denied error
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class RequestError(HandlerError):
|
||||
'''
|
||||
Request is invalid error
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class ResponseError(HandlerError):
|
||||
'''
|
||||
Generic response error
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
class Handler(object):
|
||||
'''
|
||||
REST requests handler base class
|
||||
'''
|
||||
raw = False # If true, Handler will return directly an HttpResponse Object
|
||||
name = None # If name is not used, name will be the class name in lower case
|
||||
path = None # Path for this method, so we can do /auth/login, /auth/logout, /auth/auths in a simple way
|
||||
@ -95,7 +112,7 @@ class Handler(object):
|
||||
self._session = SessionStore(session_key=self._authToken)
|
||||
if 'REST' not in self._session:
|
||||
raise Exception() # No valid session, so auth_token is also invalid
|
||||
except:
|
||||
except Exception: # Couldn't authenticate
|
||||
self._authToken = None
|
||||
self._session = None
|
||||
|
||||
@ -109,26 +126,44 @@ class Handler(object):
|
||||
raise AccessDenied()
|
||||
|
||||
def headers(self):
|
||||
'''
|
||||
Returns the headers of the REST request (all)
|
||||
'''
|
||||
return self._headers
|
||||
|
||||
def header(self, header_):
|
||||
return self._headers.get(header_)
|
||||
def header(self, headerName):
|
||||
'''
|
||||
Get's an specific header name from REST request
|
||||
'''
|
||||
return self._headers.get(headerName)
|
||||
|
||||
def addHeader(self, header, value):
|
||||
'''
|
||||
Inserts a new header inside the headers list
|
||||
'''
|
||||
self._headers[header] = value
|
||||
|
||||
def removeHeader(self, header):
|
||||
'''
|
||||
Removes an specific header from the headers list
|
||||
'''
|
||||
try:
|
||||
del self._headers[header]
|
||||
except:
|
||||
pass
|
||||
except Exception:
|
||||
pass # If not found, just ignore it
|
||||
|
||||
# Auth related
|
||||
def getAuthToken(self):
|
||||
'''
|
||||
Returns the authentication token for this REST request
|
||||
'''
|
||||
return self._authToken
|
||||
|
||||
@staticmethod
|
||||
def storeSessionAuthdata(session, id_auth, username, locale, is_admin, staff_member):
|
||||
'''
|
||||
Stores the authentication data inside current session
|
||||
'''
|
||||
if is_admin:
|
||||
staff_member = True # Make admins also staff members :-)
|
||||
|
||||
@ -141,6 +176,10 @@ class Handler(object):
|
||||
}
|
||||
|
||||
def genAuthToken(self, id_auth, username, locale, is_admin, staf_member):
|
||||
'''
|
||||
Generates the authentication token from a session, that is basically
|
||||
the session key itself
|
||||
'''
|
||||
session = SessionStore()
|
||||
session.set_expiry(GlobalConfig.ADMIN_IDLE_TIME.getInt())
|
||||
Handler.storeSessionAuthdata(session, id_auth, username, locale, is_admin, staf_member)
|
||||
@ -150,6 +189,9 @@ class Handler(object):
|
||||
return self._authToken
|
||||
|
||||
def cleanAuthToken(self):
|
||||
'''
|
||||
Cleans up the authentication token
|
||||
'''
|
||||
self._authToken = None
|
||||
if self._session:
|
||||
self._session.delete()
|
||||
@ -157,21 +199,33 @@ class Handler(object):
|
||||
|
||||
# Session related (from auth token)
|
||||
def getValue(self, key):
|
||||
'''
|
||||
Get REST session related value for a key
|
||||
'''
|
||||
try:
|
||||
return self._session['REST'].get(key)
|
||||
except:
|
||||
return None
|
||||
except Exception:
|
||||
return None # _session['REST'] does not exists?
|
||||
|
||||
def setValue(self, key, value):
|
||||
'''
|
||||
Set a session key value
|
||||
'''
|
||||
try:
|
||||
self._session['REST'][key] = value
|
||||
self._session.accessed = True
|
||||
self._session.save()
|
||||
except:
|
||||
pass
|
||||
except Exception:
|
||||
logger.exception('Got an exception setting session value {} to {}'.format(key, value))
|
||||
|
||||
def is_admin(self):
|
||||
'''
|
||||
True if user of this REST request is administrator
|
||||
'''
|
||||
return self.getValue('is_admin') and True or False
|
||||
|
||||
def is_staff_member(self):
|
||||
'''
|
||||
True if user of this REST request is member of staff
|
||||
'''
|
||||
return self.getValue('staff_member') and True or False
|
||||
|
@ -199,7 +199,7 @@ class Transports(DetailHandler):
|
||||
return [{
|
||||
'id': i.id,
|
||||
'name': i.name,
|
||||
'type': self.type_as_dict(i.getType()),
|
||||
'type': self.typeAsDict(i.getType()),
|
||||
'comments': i.comments,
|
||||
'priority': i.priority,
|
||||
} for i in parent.transports.all()]
|
||||
|
@ -37,14 +37,14 @@ from django.utils.translation import ugettext as _
|
||||
from django.db import IntegrityError
|
||||
|
||||
from uds.core.ui.UserInterface import gui as uiGui
|
||||
from uds.REST.handlers import Handler
|
||||
from uds.REST.handlers import Handler, HandlerError
|
||||
from uds.core.util import log
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__updated__ = '2014-05-19'
|
||||
__updated__ = '2014-09-15'
|
||||
|
||||
|
||||
# a few constants
|
||||
@ -56,7 +56,10 @@ LOG = 'log'
|
||||
|
||||
|
||||
# Exception to "rethrow" on save error
|
||||
class SaveException(Exception):
|
||||
class SaveException(HandlerError):
|
||||
'''
|
||||
Exception thrown if couldn't save
|
||||
'''
|
||||
pass
|
||||
|
||||
|
||||
@ -64,6 +67,9 @@ class SaveException(Exception):
|
||||
class BaseModelHandler(Handler):
|
||||
|
||||
def addField(self, gui, field):
|
||||
'''
|
||||
Add a field to a "gui" description
|
||||
'''
|
||||
gui.append({
|
||||
'name': field.get('name', ''),
|
||||
'value': '',
|
||||
@ -84,6 +90,9 @@ class BaseModelHandler(Handler):
|
||||
return gui
|
||||
|
||||
def addDefaultFields(self, gui, flds):
|
||||
'''
|
||||
Adds default fields (based in a list) to a "gui" description
|
||||
'''
|
||||
if 'name' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'name',
|
||||
@ -94,40 +103,47 @@ class BaseModelHandler(Handler):
|
||||
})
|
||||
if 'comments' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'comments',
|
||||
'label': _('Comments'),
|
||||
'tooltip': _('Comments for this element'),
|
||||
'length': 256,
|
||||
'order': 0 - 99,
|
||||
'name': 'comments',
|
||||
'label': _('Comments'),
|
||||
'tooltip': _('Comments for this element'),
|
||||
'length': 256,
|
||||
'order': 0 - 99,
|
||||
})
|
||||
if 'priority' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'priority',
|
||||
'type': 'numeric',
|
||||
'label': _('Priority'),
|
||||
'tooltip': _('Selects the priority of this element (lower number means higher priority)'),
|
||||
'required': True,
|
||||
'value': 1,
|
||||
'length': 4,
|
||||
'order': 0 - 98,
|
||||
'name': 'priority',
|
||||
'type': 'numeric',
|
||||
'label': _('Priority'),
|
||||
'tooltip': _('Selects the priority of this element (lower number means higher priority)'),
|
||||
'required': True,
|
||||
'value': 1,
|
||||
'length': 4,
|
||||
'order': 0 - 98,
|
||||
})
|
||||
if 'small_name' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'small_name',
|
||||
'type': 'text',
|
||||
'label': _('Short name'),
|
||||
'tooltip': _('Short name of this element'),
|
||||
'required': True,
|
||||
'length': 128,
|
||||
'order': 0 - 97,
|
||||
'name': 'small_name',
|
||||
'type': 'text',
|
||||
'label': _('Short name'),
|
||||
'tooltip': _('Short name of this element'),
|
||||
'required': True,
|
||||
'length': 128,
|
||||
'order': 0 - 97,
|
||||
})
|
||||
|
||||
return gui
|
||||
|
||||
def typeInfo(self, type_):
|
||||
'''
|
||||
Returns info about the type
|
||||
In fact, right now, it returns an empty dict, that will be extended by typeAsDict
|
||||
'''
|
||||
return {}
|
||||
|
||||
def type_as_dict(self, type_):
|
||||
def typeAsDict(self, type_):
|
||||
'''
|
||||
Returns a dictionary describing the type (the name, the icon, description, etc...)
|
||||
'''
|
||||
res = self.typeInfo(type_)
|
||||
res.update({
|
||||
'name': _(type_.name()),
|
||||
@ -138,6 +154,9 @@ class BaseModelHandler(Handler):
|
||||
return res
|
||||
|
||||
def processTableFields(self, title, fields, row_style):
|
||||
'''
|
||||
Returns a dict containing the table fields description
|
||||
'''
|
||||
return {
|
||||
'title': unicode(title),
|
||||
'fields': fields,
|
||||
@ -369,10 +388,11 @@ class ModelHandler(BaseModelHandler):
|
||||
needs_staff = True
|
||||
# Which model does this manage
|
||||
model = None
|
||||
|
||||
# This is an array of tuples of two items, where first is method and second inticates if method needs parent id
|
||||
# For example ('services', True) -- > .../id_parent/services
|
||||
# ('services', False) --> ..../services
|
||||
custom_methods = [] # If this model respond to "custom" methods, we will declare them here
|
||||
# This is an array of tuples of two items, where first is method and second inticates if method needs parent id
|
||||
# For example ('services', True) -- > .../id_parent/services
|
||||
# ('services', False) --> ..../services
|
||||
# If this model has details, which ones
|
||||
detail = None # Dictionary containing detail routing
|
||||
# Put needed fields
|
||||
@ -394,7 +414,7 @@ class ModelHandler(BaseModelHandler):
|
||||
|
||||
def getTypes(self, *args, **kwargs):
|
||||
for type_ in self.enum_types():
|
||||
yield self.type_as_dict(type_)
|
||||
yield self.typeAsDict(type_)
|
||||
|
||||
def getType(self, type_):
|
||||
found = None
|
||||
@ -480,14 +500,14 @@ class ModelHandler(BaseModelHandler):
|
||||
try:
|
||||
operation = getattr(self, self._args[1])
|
||||
item = self.model.objects.get(pk=self._args[0])
|
||||
except:
|
||||
except Exception:
|
||||
self.invalidMethodException()
|
||||
|
||||
return operation(item)
|
||||
elif self._args[0] == cm[0]:
|
||||
try:
|
||||
operation = getattr(self, self._args[0])
|
||||
except:
|
||||
except Exception:
|
||||
self.invalidMethodException()
|
||||
|
||||
return operation()
|
||||
@ -538,6 +558,9 @@ class ModelHandler(BaseModelHandler):
|
||||
self.invalidRequestException()
|
||||
|
||||
def post(self):
|
||||
'''
|
||||
Processes a POST request
|
||||
'''
|
||||
# right now
|
||||
logger.debug('method POST for {0}, {1}'.format(self.__class__.__name__, self._args))
|
||||
if len(self._args) == 2:
|
||||
|
@ -32,9 +32,10 @@
|
||||
from __future__ import unicode_literals
|
||||
from django.conf.urls import patterns
|
||||
|
||||
__updated__ = '2014-02-19'
|
||||
__updated__ = '2014-09-15'
|
||||
|
||||
urlpatterns = patterns('uds.admin.views',
|
||||
urlpatterns = patterns(
|
||||
'uds.admin.views',
|
||||
(r'^$', 'index'),
|
||||
(r'^tmpl/(?P<template>[a-zA-Z0-9_-]*)$', 'tmpl'),
|
||||
(r'^sample$', 'sample'),
|
||||
|
@ -41,7 +41,7 @@ from uds.core.util.decorators import denyBrowsers
|
||||
|
||||
import logging
|
||||
|
||||
__updated__ = '2014-06-11'
|
||||
__updated__ = '2014-09-15'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -43,7 +43,7 @@ from uds.core.ui.UserInterface import gui
|
||||
|
||||
import logging
|
||||
|
||||
__updated__ = '2014-06-11'
|
||||
__updated__ = '2014-09-15'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -36,12 +36,12 @@ js_info_dict = {
|
||||
}
|
||||
|
||||
|
||||
from django.conf import settings
|
||||
from django.conf.urls import patterns, include, url
|
||||
from uds.core.util.modfinder import loadModulesUrls
|
||||
from uds import REST
|
||||
|
||||
urlpatterns = patterns('uds',
|
||||
urlpatterns = patterns(
|
||||
'uds',
|
||||
(r'^$', 'web.views.index'),
|
||||
(r'^login/$', 'web.views.login'),
|
||||
(r'^login/(?P<smallName>.+)$', 'web.views.login'),
|
||||
|
@ -34,13 +34,13 @@ This package contains all xmlrpc related stuff
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
# from django.utils.translation import ugettext_noop as _
|
||||
|
||||
from uds.core.managers.DownloadsManager import DownloadsManager
|
||||
import os.path
|
||||
import sys
|
||||
# from uds.core.managers.DownloadsManager import DownloadsManager
|
||||
# import os.path
|
||||
# import sys
|
||||
|
||||
DownloadsManager.manager().registerDownloadable('UDSAdminSetup.exe',
|
||||
_('UDS Client Administration Interface <b>(Important!! Requires .net framework 3.5 sp1)</b>'),
|
||||
os.path.dirname(sys.modules[__package__].__file__) + '/files/UDSAdminSetup.exe',
|
||||
'application/x-msdownload')
|
||||
# DownloadsManager.manager().registerDownloadable('UDSAdminSetup.exe',
|
||||
# _('UDS Client Administration Interface <b>(Important!! Requires .net framework 3.5 sp1)</b>'),
|
||||
# os.path.dirname(sys.modules[__package__].__file__) + '/files/UDSAdminSetup.exe',
|
||||
# 'application/x-msdownload')
|
||||
|
@ -31,7 +31,6 @@
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
|
||||
from django.db import transaction
|
||||
from uds.models import UserService
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util import log
|
||||
@ -48,7 +47,8 @@ def test():
|
||||
logger.debug("Test called")
|
||||
return True
|
||||
|
||||
def message(id_, message, data):
|
||||
|
||||
def message_fnc(id_, message, data):
|
||||
'''
|
||||
Process a message from the actor.
|
||||
@param _id: Ids used by actors to identify themself and locate services to witch they belongs
|
||||
@ -84,4 +84,4 @@ def registerActorFunctions(dispatcher):
|
||||
Utility function to register methods at xmlrpc server for actor
|
||||
'''
|
||||
dispatcher.register_function(test, 'test')
|
||||
dispatcher.register_function(message, 'message')
|
||||
dispatcher.register_function(message_fnc, 'message')
|
||||
|
@ -40,7 +40,7 @@ from django.views.decorators.csrf import csrf_exempt
|
||||
# from services.DeployedServices import registerDeployedServicesFunctions
|
||||
# from services.Publications import registerPublicationsFunctions
|
||||
# from services.UserDeployedServices import registerUserDeployedServiceFunctions
|
||||
from actor.Actor import registerActorFunctions
|
||||
from uds.xmlrpc.actor.Actor import registerActorFunctions
|
||||
# from util.Callbacks import registerCallbackFunctions
|
||||
# from auths.AdminAuth import registerAdminAuthFunctions
|
||||
# from auths.Authenticators import registerAuthenticatorFunctions
|
||||
@ -59,6 +59,7 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class XMLRPCDispatcher(SimpleXMLRPCDispatcher):
|
||||
'''
|
||||
Own dispatchers, to allow the pass of the request to the methods.
|
||||
@ -73,7 +74,6 @@ class XMLRPCDispatcher(SimpleXMLRPCDispatcher):
|
||||
def __init__(self):
|
||||
SimpleXMLRPCDispatcher.__init__(self, allow_none=False, encoding=None)
|
||||
|
||||
|
||||
def dispatch(self, request, **kwargs):
|
||||
import xmlrpclib
|
||||
xml = request.body
|
||||
@ -91,13 +91,15 @@ class XMLRPCDispatcher(SimpleXMLRPCDispatcher):
|
||||
except Exception as e:
|
||||
response = xmlrpclib.dumps(
|
||||
xmlrpclib.Fault(1, "Exception caught!: {0}".format(e))
|
||||
)
|
||||
)
|
||||
logger.exception('Excaption processing xmlrpc message')
|
||||
|
||||
return HttpResponse(response, content_type='text/xml')
|
||||
|
||||
|
||||
dispatcher = XMLRPCDispatcher()
|
||||
|
||||
|
||||
# csrf_exempt is needed because we don't expect xmlrcp to be called from a web form
|
||||
@csrf_exempt
|
||||
def xmlrpc(request):
|
||||
@ -108,20 +110,6 @@ def xmlrpc(request):
|
||||
response = HttpResponse()
|
||||
response.write("<b>This is an XML-RPC Service.</b><br>")
|
||||
response.write("You need to invoke it using an XML-RPC Client!<br>")
|
||||
# response.write("The following methods are available:<ul>")
|
||||
# methods = dispatcher.system_listMethods()
|
||||
# for method in methods:
|
||||
# right now, my version of SimpleXMLRPCDispatcher always
|
||||
# returns "signatures not supported"... :(
|
||||
# but, in an ideal world it will tell users what args are expected
|
||||
# sig = dispatcher.system_methodSignature(method)
|
||||
|
||||
# this just reads your docblock, so fill it in!
|
||||
# help = dispatcher.system_methodHelp(method)
|
||||
|
||||
# response.write("<li><b>%s</b>: [%s] %s" % (method, sig, help))
|
||||
|
||||
# response.write("</ul>")
|
||||
|
||||
response['Content-length'] = str(len(response.content))
|
||||
return response
|
||||
|
Loading…
Reference in New Issue
Block a user