LEARN THE REALLY HARD WAY Anthony Bastardi
ADVICE FROM AN OLD PROGRAMMER [Use] code as your secret weapon...People who can code in biology, medicine, government, sociology, physics, history, and mathematics are respected and can do amazing things to advance those disciplines. Learn Python the Hard Way, Zed Shaw
I believe that the ability to easily build high quality web applications is of critical importance for the growth of a free and open society...As you will learn in the following pages, web2py tries to lower the barrier of entry to web development... web2py: Complete Reference Manual, 6th Edition, Massimo Di Pierro
ANSWERING QUESTIONS
SOME BENEFITS OF ANSWERING QUESTIONS Get exposure to real-world problems. Get exposure to all aspects of web development. Discover common pitfalls. Explaining/teaching leads to deeper understanding. Make serendipitous discoveries. Develop debugging and code review skills. Collect usable code. Help other people.
HOW?
READ THE SOURCE The ultimate API documentation. Pick up Python idioms and patterns. On-ramp to making contributions.
USE THE WEB2PY SHELL Experiment safely. Inspect/explore objects. Quickly iterate to build code.
WEB2PY WSGI APPLICATION
WSGIBASE OVERVIEW globals– Request, Response, Session rewrite.url_in– Routing Handle static files Additional set-up (cookies, session, update Request) main.serve_controller– Execute app code compileapp build_environment run_models_in> restricted.restricted run_controller_in> restricted.restricted run_view_in> restricted.restricted Wrap up (commit/rollback, close session, handle errors, etc.) http.HTTP– Return response * Note, all modules above are in the gluon namespace.
main.wsgibase def wsgibase(environ, responder): eget = environ.get current.__dict__.clear() request = Request(environ) response = Response() session = Session()
globals.Request class Request(Storage): def __init__(self, env): Storage.__init__(self) self.env = Storage(env) self.env.web2py_path = global_settings.applications_parent self.env.update(global_settings) self.cookies = Cookie.SimpleCookie() self._get_vars = None self._post_vars = None self._vars = None self._body = None self.folder = None self.application = None self.function = None self.args = List() self.extension = 'html' self.now = datetime.datetime.now() self.utcnow = datetime.datetime.utcnow() self.is_restful = False self.is_https = False self.is_local = False self.global_settings = settings.global_settings
main.wsgibase try: try: try: fixup_missing_path_info(environ) (static_file, version, environ) = url_in(request, environ) response.status = env.web2py_status_code or response.status if static_file: if eget('QUERY_STRING', '').startswith('attachment'): response.headers['Content-Disposition'] \ = 'attachment' if version: response.headers['Cache-Control'] = 'max-age=315360000' response.headers[ 'Expires'] = 'Thu, 31 Dec 2037 23:59:59 GMT' response.stream(static_file, request=request)
main.wsgibase # ################################################## # run controller # ################################################## if global_settings.debugging and app != "admin": import gluon.debug # activate the debugger gluon.debug.dbg.do_debug(mainpyfile=request.folder) serve_controller(request, response, session)
main.serve_controller def serve_controller(request, response, session): # ################################################## # build environment for controller and view # ################################################## environment = build_environment(request, response, session) response.view = '%s/%s.%s' % (request.controller, request.function, request.extension) # ################################################## # process models, controller and view (if required) # ################################################## run_models_in(environment) response._view_environment = copy.copy(environment) page = run_controller_in(request.controller, request.function, environment) if isinstance(page, dict): response._vars = page response._view_environment.update(page) run_view_in(response._view_environment) page = response.body.getvalue()
compileapp _base_environment_ = dict((k, getattr(html, k)) for k in html.__all__) _base_environment_.update( (k, getattr(validators, k)) for k in validators.__all__) _base_environment_['__builtins__'] = __builtins__ _base_environment_['HTTP'] = HTTP _base_environment_['redirect'] = redirect _base_environment_['DAL'] = DAL _base_environment_['Field'] = Field _base_environment_['SQLDB'] = SQLDB # for backward compatibility _base_environment_['SQLField'] = SQLField # for backward compatibility _base_environment_['SQLFORM'] = SQLFORM _base_environment_['SQLTABLE'] = SQLTABLE _base_environment_['LOAD'] = LOAD
compileapp.build_environmen t def build_environment(request, response, session, store_current=True): environment = dict(_base_environment_) if not request.env: request.env = Storage() response.models_to_run = [ r'^\w+\.py$', r'^%s/\w+\.py$' % request.controller, r'^%s/%s/\w+\.py$' % (request.controller, request.function) ] t = environment['T'] = translator(os.path.join(request.folder,'languages'), request.env.http_accept_language) c = environment['cache'] = Cache(request)
compileapp.build_environmen t environment['request'] = request environment['response'] = response environment['session'] = session environment['local_import'] = \ lambda name, reload=False, app=request.application:\ local_import_aux(name, reload, app) BaseAdapter.set_folder(pjoin(request.folder, 'databases')) response._view_environment = copy.copy(environment) custom_import_install() return environment
compileapp.models_to_run def run_models_in(environment): """ Runs all models (in the app specified by the current folder) It tries pre-compiled models first before compiling them. """ folder = environment['request'].folder c = environment['request'].controller f = environment['request'].function response = environment['response'] path = pjoin(folder, 'models') cpath = pjoin(folder, 'compiled') compiled = os.path.exists(cpath) if compiled: models = sorted(listdir(cpath, '^models[_.][\w.]+\.pyc$', 0), model_cmp) else: models = sorted(listdir(path, '^\w+\.py$', 0, sort=False), model_cmp_sep) models_to_run = None
compileapp.models_to_run for model in models: if response.models_to_run != models_to_run: regex = models_to_run = response.models_to_run[:] if isinstance(regex, list): regex = re_compile('|'.join(regex)) if models_to_run: if compiled: n = len(cpath)+8 fname = model[n:-4].replace('.','/')+'.py' else: n = len(path)+1 fname = model[n:].replace(os.path.sep,'/') if not regex.search(fname) and c != 'appadmin': continue elif compiled: code = read_pyc(model) elif is_gae: code = getcfs(model, model, lambda: compile2(read_file(model), model)) else: code = getcfs(model, model, None) restricted(code, environment, layer=model)
restricted.restricted def restricted(code, environment=None, layer='Unknown'): """ Runs code in environment and returns the output. If an exception occurs in code it raises a RestrictedError containing the traceback. """ if environment is None: environment = {} environment['__file__'] = layer environment['__name__'] = '__restricted__' try: if isinstance(code, types.CodeType): ccode = code else: ccode = compile2(code, layer) exec ccode in environment except HTTP: raise except RestrictedError: raise except Exception, error: etype, evalue, tb = sys.exc_info() if __debug__ and 'WINGDB_ACTIVE' in os.environ: sys.excepthook(etype, evalue, tb) output = "%s %s" % (etype, evalue) raise RestrictedError(layer, code, output, environment)
main.serve_controller # ################################################## # set default headers it not set # ################################################## default_headers = [ ('Content-Type', contenttype('.' + request.extension)), ('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0'), ('Expires', time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime())), ('Pragma', 'no-cache')] for key, value in default_headers: response.headers.setdefault(key, value) raise HTTP(response.status, page, **response.headers)
main.wsgibase serve_controller(request, response, session) except HTTP, http_response: if static_file: return http_response.to(responder, env=env) if request.body: request.body.close() if hasattr(current,'request'): session._try_store_in_db(request, response) if response.do_not_commit is True: BaseAdapter.close_all_instances(None) elif response.custom_commit: BaseAdapter.close_all_instances(response.custom_commit) else: BaseAdapter.close_all_instances('commit')
main.wsgibase except RestrictedError, e: if request.body: request.body.close() # ################################################## # on application error, rollback database # ################################################## # log tickets before rollback if not in DB if not request.tickets_db: ticket = e.log(request) or 'unknown' if response._custom_rollback: response._custom_rollback() else: BaseAdapter.close_all_instances('rollback') if request.tickets_db: ticket = e.log(request) or 'unknown' http_response = \ HTTP(500, rwthread.routes.error_message_ticket % dict(ticket=ticket), web2py_error='ticket %s' % ticket)
main.wsgibase finally: if response and hasattr(response, 'session_file') \ and response.session_file: response.session_file.close() session._unlock(response) http_response, new_environ = try_rewrite_on_error( http_response, request, environ, ticket) if not http_response: return wsgibase(new_environ, responder) if global_settings.web2py_crontype == 'soft': newcron.softcron(global_settings.applications_parent).start() return http_response.to(responder, env=env)
http.HTTP def to(self, responder, env=None): env = env or {} status = self.status headers = self.headers if status in defined_status: status = '%d %s' % (status, defined_status[status]) elif isinstance(status, int): status = '%d UNKNOWN ERROR' % status else: status = str(status) if not regex_status.match(status): status = '500 %s' % (defined_status[500])
http.HTTP rheaders = [] for k, v in headers.iteritems(): if isinstance(v, list): rheaders += [(k, str(item)) for item in v] elif not v is None: rheaders.append((k, str(v))) responder(status, rheaders) if env.get('request_method', '') == 'HEAD': return [''] elif isinstance(body, str): return [body] elif hasattr(body, '__iter__'): return body else: return [str(body)]
LOCATIONS OF GLOBAL API OBJECTS cache: cache compileapp: LOAD dal: DAL, Field globals: request, response, session http: HTTP, redirect html: HTML helpers, URL languages: T sqlhtml: SQLFORM, SQLFORM.grid, SQLTABLE validators: All validators
http://web2py.readthedocs.com Docs » Welcome to web2py’s API documentation!
Welcome to web2py’s API documentation! Contents: admin
Module
Utility functions for the Admin application cache Module Basic caching classes and methods cfs Module Functions required to execute app components compileapp Module Functions required to execute app components contenttype Module custom_import
Module
Support for smart import syntax for web2py applications dal Module debug
Read the Docs
Module
Debugger support classes decoder Module v: latest
fileutils
Module
And now, the web2py shell...
BONUS ROUND SECRET FEATURES
ADD AND REMOVE CLASSES FROM HTML HELPERS mydiv.add_class('new_class') mydiv.remove_class('old_class')
CONDITIONAL EXTEND AND INCLUDE IN TEMPLATES {{extend 'mylayout.html' if some_condition else None}} {{include 'myview.html' if some_condition else None}}
Warning: Views with a conditional extend/include cannot be compiled.
CLONE A FIELD db.define_table('table2', db.table1.field1.clone(...))
SQL STRING AS QUERY db("mytable.start_date < NOW()").select(db.mytable.ALL)
ADD METHODS TO DAL TABLES @db.person.add_method.just_men @db.animal.add_method.just_males def get_just_gender_equals_m(self): return self._db(self.gender == 'm').select() db.person.just_men() db.animal.just_males()
SELECT FOR UPDATE db(query).select(..., for_update=True)
CUSTOM PROCESSOR FOR QUERY RESULTS db(query).select(..., processor=lambda rows, fields, colnames: ...)
CATCH DATABASE ERRORS
try: db.mytable.insert(**fields) except (db._adapter.driver.IntegrityError, db._adapter.driver.OperationalError) as e: pass
or db.mytable._on_insert_error = my_insert_error_handler db.mytable._on_update_error = my_update_error_handler
CONVERT FORMS form.as_dict() form.as_json() form.as_xml() form.as_yaml()
ADDITIONAL IS_IN_DBARGUMENTS IS_IN_DB(..., groupby=..., orderby=..., sort=...)
ajaxFUNCTION ARGUMENTS // Second argument can be a jQuery selector instead of a list of names. ajax('/myapp/default/myaction', '.some_selector', 'target'); // Third argument can be a function instead of an ID or ':eval'. ajax('/myapp/default/myaction', [], function(server_response) {...});
THE END