def confirmPersonCandidate(ticket): 'Move changes from the PersonCandidate table into the Person table' # Query candidate = Session.query(model.PersonCandidate).filter(model.PersonCandidate.ticket==ticket).filter(model.PersonCandidate.when_expired>=datetime.datetime.utcnow()).first() # If the ticket exists, if candidate: # If the person exists, if candidate.person_id: # Update person person = Session.query(model.Person).get(candidate.person_id) person.username = candidate.username person.password_hash = candidate.password_hash person.nickname = candidate.nickname person.email = candidate.email person.email_sms = candidate.email_sms # Reset rejection_count person.rejection_count = 0 # If the person does not exist, else: # Add person Session.add(model.Person(candidate.username, candidate.password_hash, candidate.nickname, candidate.email, candidate.email_sms)) # Delete ticket Session.delete(candidate) # Commit Session.commit() # Return return candidate
def saveResult(outgoingMessage): 'Save result in local database' # Unpack result from outgoing message outgoingPack = pickle.loads(outgoingMessage.body) scenarioID, scenarioOutput, scenarioData, scenarioStatus = outgoingPack print 'Consuming scenario %s' % scenarioID # Load scenario from local database scenario = Session.query(model.Scenario).get(scenarioID) # If the scenario does not exist, if not scenario: print 'Scenario %s does not exist' % scenarioID return # Get folder scenarioFolder = scenario.getFolder() # Save data if scenarioData: scenarioPath = scenarioFolder + '.zip' open(scenarioPath, 'wb').write(scenarioData) store.unzipData(scenarioFolder, scenarioData) # Save output scenario.output = scenarioOutput scenario.status = scenarioStatus Session.commit() # Post to callback scenario.postCallback()
def check(self, scenarioID): # Initialize personID = h.getPersonID() # Load scenario = Session.query(model.Scenario).filter(model.Scenario.id==scenarioID).filter(model.getScopeFilter(personID)).first() # Return return dict(isOk=0 if not scenario or scenario.isQueued() else 1)
def update(self, id): 'PUT /scenarios/id: Update an existing item' # Initialize personID = h.getPersonID() # Load scenario = Session.query(model.Scenario).filter(model.Scenario.id==id).first() # If the scenario doesn't exist, if not scenario: return dict(isOk=0, message='Scenario %s does not exist' % id) # If the user is not the owner, if personID != scenario.owner_id: return dict(isOk=0, message='You are not the owner of scenario %s' % id) # Load scenarioName = request.params.get('scenarioName', '').strip() if not scenarioName: return dict(isOk=0, message='Please enter a scenario name') try: scenarioScope = int(request.params.get('scenarioScope')) except ValueError: return dict(isOk=0, message='Scenario scope must be an integer') if scenarioScope not in [model.scopePrivate, model.scopePublic]: return dict(isOk=0, message='Scenario scope can either be %s=private or %s=public' % (model.scopePrivate, model.scopePublic)) # Update scenario.name = scenarioName scenario.scope = scenarioScope # Commit Session.commit() # Return return dict(isOk=1)
def feedback(self): 'Send feedback' # Load text = request.POST.get('text', '').strip() # If there is text, if text: # Initialize personID = h.getPersonID() headerByValue = {} # If the person is logged in, if personID: # Load person = Session.query(model.Person).get(personID) nickname = person.nickname headerByValue['reply-to'] = email.utils.formataddr((nickname, person.email)) # If th person is not logged in, else: nickname = 'Anonymous' # Send it subject = '[%s] Feedback from %s' % (parameter.SITE_NAME, nickname) try: smtp.sendMessage( config['safe']['mail support'], config['safe']['mail support'], subject, text, headerByValue) except: return dict(isOk=0, message='Error sending message') # Return return dict(isOk=1) # Return return dict(isOk=0)
def kill(self, jobID, host): 'Attempt to kill the job' job = Session.query(model.Job).filter(model.Job.pid == jobID and model.Job.host == host).first() if (not job) or (job.end_time) or (not h.isAdmin()): return dict(isOk=0) else: job.kill() return dict(isOk=1)
def log(self, jobID, host): 'Show the log for the job' job = Session.query(model.Job).filter(model.Job.pid == jobID and model.Job.host == host).first() filepath = job.log_filename file_size = os.path.getsize(filepath) headers = [('Content-Type', 'text/plain'), ('Content-Length', str(file_size))] fapp = FileApp(filepath, headers=headers) return fapp(request.environ, self.start_response)
def index(self): "Show processors that have updated in the last hour" c.processors = ( Session.query(model.Processor) .filter(model.Processor.when_updated > datetime.datetime.utcnow() - datetime.timedelta(days=1)) .order_by(model.Processor.when_updated.desc()) .all() ) return render("/processors/index.mako")
def _to_python(self, value, person): "Check whether the value is unique" # If the person is new or the value changed, if not person or getattr(person, self.fieldName) != value: # Make sure the value is unique if Session.query(model.Person).filter(getattr(model.Person, self.fieldName) == value).first(): # Raise raise formencode.Invalid(self.errorMessage, value, person) # Return return value
def update_(self): "Send update confirmation email" # Load personID = h.getPersonID() # If the person is not logged in, if not personID: return dict(isOk=0) # Prepare person = Session.query(model.Person).get(personID) # Return return changeAccount(dict(request.POST), "update", "/people/confirm.mako", person)
def extractConfigurationByName(valueByName, scenarioFolder): # Initialize configuration = {} # For each value, for key, value in valueByName.iteritems(): # Parse key keyTerms = variable_store.parseKey(key) # If the key is compound, if len(keyTerms) > 1: # Extract modelType, section, option = keyTerms # If the value already exists, then it must have been overridden if modelType in configuration: if section in configuration[modelType]: if option in configuration[modelType][section]: continue # If we have a hidden field, if option.endswith('_h'): # If the hidden field was overridden, skip this and wait until we find the real value if int(value) == 0: continue # Remove suffix option = option[:-2] # Prepare sourceScenario = Session.query(model.Scenario).get(value) relativePath = sourceScenario.input['%s configuration' % modelType][section][option] # If the old scenario did not specify a file here, if not relativePath: value = '' else: # Prepare sourcePath = os.path.join(sourceScenario.getFolder(), relativePath) # Copy source in case it is deleted store.makeFolderSafely(os.path.join(scenarioFolder, os.path.dirname(relativePath))) targetPath = os.path.join(scenarioFolder, relativePath) shutil.copyfile(sourcePath, targetPath) value = relativePath # If the user wants to use a new file and the value is an upload, elif hasattr(value, 'file'): # Prepare relativePath = os.path.join(modelType, section, option + os.path.splitext(value.filename)[1]) # Save it store.makeFolderSafely(os.path.join(scenarioFolder, os.path.dirname(relativePath))) targetPath = os.path.join(scenarioFolder, relativePath) shutil.copyfileobj(value.file, open(targetPath, 'wb')) value = relativePath # Store if modelType not in configuration: configuration[modelType] = {} if section not in configuration[modelType]: configuration[modelType][section] = {} configuration[modelType][section][option] = value # Return return configuration
def update(self): "Update processor information" # Load ip = h.getRemoteIP() processor = Session.query(model.Processor).filter(model.Processor.ip == ip).first() # If the processor doesn't exist, if not processor: processor = model.Processor(ip) Session.add(processor) # Update processor.when_updated = datetime.datetime.utcnow() Session.commit()
def delete(self, id): 'DELETE /scenarios/id: Delete an existing item' # Initialize personID = h.getPersonID() # Load scenario = Session.query(model.Scenario).filter(model.Scenario.id==id).first() # If the scenario doesn't exist, if not scenario: return dict(isOk=0, message='Scenario %s does not exist' % id) # If the user is not the owner, if personID != scenario.owner_id: return dict(isOk=0, message='You are not the owner of scenario %s' % id) # Delete Session.delete(scenario) Session.commit() # Return return dict(isOk=1)
def reset(self): 'Reset password' # Get email email = request.POST.get('email') # Try to load the person person = Session.query(model.Person).filter(model.Person.email==email).first() # If the email is not in our database, if not person: return dict(isOk=0) # Reset account c.password = store.makeRandomAlphaNumericString(parameter.PASSWORD_LENGTH_AVERAGE) return changeAccount(dict( username=person.username, password=c.password, nickname=person.nickname, email=person.email, email_sms=person.email_sms, ), 'reset', '/people/confirm.mako', person)
def update(self): 'Show account update page' # Load personID = h.getPersonID() # If the person is not logged in, if not personID: # Return return redirect(url('person_login', targetURL=h.encodeURL('/'))) # Render c.isNew = False person = Session.query(model.Person).get(personID) # Return return formencode.htmlfill.render(render('/people/change.mako'), { 'username': person.username, 'nickname': person.nickname, 'email': person.email, 'email_sms': person.email_sms, })
def clone(self, scenarioID): 'Show form to create a new item based on datasets and parameters from existing scenario' # Make sure the user is logged in personID = h.getPersonID() if not personID: return redirect(url('person_login', targetURL=h.encodeURL(request.path))) # Make sure the user has access to the scenario scenario = Session.query(model.Scenario).filter(model.getScopeFilter(personID)).filter(model.Scenario.id==scenarioID).first() if not scenario: return redirect(url('new_scenario')) # Load scenarioInput = scenario.input # Prepare c.scenario = scenario c.metricModel = metric.getModel(request.GET.get('metricModel', scenarioInput['metric model name'])) c.metricConfiguration = scenarioInput['metric configuration'] c.networkModel = network.getModel(request.GET.get('networkModel', scenarioInput['network model name'])) c.networkConfiguration = scenarioInput['network configuration'] # Return return render('/scenarios/new.mako')
def login_(self): 'Process login credentials' # Check username username = str(request.POST.get('username', '')) person = Session.query(model.Person).filter_by(username=username).first() # If the username does not exist, if not person: return dict(isOk=0) # Check password password_hash = model.hashString(str(request.POST.get('password', ''))) # If the password is incorrect, if password_hash != StringIO.StringIO(person.password_hash).read(): # Increase and return rejection_count without a requery rejection_count = person.rejection_count = person.rejection_count + 1 Session.commit() return dict(isOk=0, rejection_count=rejection_count) # If there have been too many rejections, if person.rejection_count >= parameter.REJECTION_LIMIT: # Expect recaptcha response recaptchaChallenge = request.POST.get('recaptcha_challenge_field', '') recaptchaResponse = request.POST.get('recaptcha_response_field', '') recaptchaKey = config['safe']['recaptcha']['private'] # Validate result = captcha.submit(recaptchaChallenge, recaptchaResponse, recaptchaKey, h.getRemoteIP()) # If the response is not valid, if not result.is_valid: return dict(isOk=0, rejection_count=person.rejection_count) # Get minutesOffset from UTC minutesOffset = h.getMinutesOffset() # Save session session['minutesOffset'] = minutesOffset session['personID'] = person.id session['nickname'] = person.nickname session['role'] = person.role session.save() # Save person person.minutes_offset = minutesOffset person.rejection_count = 0 Session.commit() # Return return dict(isOk=1)
def update(self): "Show account update page" # Load personID = h.getPersonID() # If the person is not logged in, if not personID: # Return return redirect(url("person_login", targetURL=h.encodeURL("/"))) # Render c.isNew = False person = Session.query(model.Person).get(personID) # Return return formencode.htmlfill.render( render("/people/change.mako"), { "username": person.username, "nickname": person.nickname, "email": person.email, "email_sms": person.email_sms, }, )
def index(self, format='html'): 'GET /scenarios: Show all items in the collection' # Initialize personID = h.getPersonID() refresh = request.GET.get('refresh', 0) scope = request.GET.get('scope', str(model.scopePrivate)) # Load scenarioQuery = Session.query(model.Scenario) if not personID: scenarioQuery = scenarioQuery.filter_by(scope=model.scopePublic) elif scope == '-': scenarioQuery = scenarioQuery.filter_by(owner_id=personID) elif scope == '*': scenarioQuery = scenarioQuery.filter(model.getScopeFilter(personID)) else: scenarioQuery = scenarioQuery.filter_by(owner_id=personID).filter_by(scope=model.scopePrivate) c.scenarios = scenarioQuery.options(orm.eagerload(model.Scenario.owner)).order_by(model.Scenario.when_created.desc()).all() # If this is not a refresh request, if not refresh: return render('/scenarios/index.mako') # If this is a refresh request, else: return render('/scenarios/scenarios.mako')
def create(self): 'POST /scenarios: Create a new item' # Initialize personID = h.getPersonID() if not personID: return redirect(url('person_login', targetURL=h.encodeURL(h.url('new_scenario')))) # Load try: demographicDatabase_h = int(request.POST.get('demographicDatabase_h', 0)) except ValueError: demographicDatabase_h = 0 if not demographicDatabase_h and 'demographicDatabase' not in request.POST: return cjson.encode(dict(isOk=0, message='The demographicDatabase field is required')) scenarioName = request.POST.get('scenarioName') or 'Untitled' try: scenarioScope = int(request.POST.get('scenarioScope', model.scopePrivate)) except ValueError: scenarioScope = model.scopePrivate metricModelName = request.POST.get('metricModelName', metric.getModelNames()[0]) networkModelName = request.POST.get('networkModelName', network.getModelNames()[0]) callbackURL = request.POST.get('callbackURL') # Create scenario scenario = model.Scenario(personID, scenarioName, scenarioScope) Session.add(scenario) Session.commit() scenarioFolder = scenario.getFolder() if os.path.exists(scenarioFolder): shutil.rmtree(scenarioFolder) store.makeFolderSafely(scenarioFolder) # If the user is using an existing demographicDatabase, if demographicDatabase_h: # Copy source in case it is deleted sourceScenario = Session.query(model.Scenario).get(demographicDatabase_h) sourceScenarioFolder = sourceScenario.getFolder() demographicFileName = sourceScenario.input['demographic file name'] demographicPath = os.path.join(scenarioFolder, demographicFileName) shutil.copyfile(os.path.join(sourceScenarioFolder, demographicFileName), demographicPath) # If the user is uploading a new demographicDatabase, else: # Save original demographicDatabase in case the user wants it later demographicDatabase = request.POST['demographicDatabase'] demographicFileExtension = os.path.splitext(demographicDatabase.filename)[1] demographicFileName = 'demographics' + demographicFileExtension demographicPath = os.path.join(scenarioFolder, demographicFileName) shutil.copyfileobj(demographicDatabase.file, open(demographicPath, 'wb')) demographicDatabase.file.close() # Store input configurationByName = extractConfigurationByName(request.POST, scenarioFolder) scenario.input = { 'demographic file name': str(demographicFileName), 'metric model name': metricModelName, 'metric configuration': configurationByName.get('metric', {}), 'network model name': networkModelName, 'network configuration': configurationByName.get('network', {}), 'callback url': callbackURL, 'host url': request.host_url, } Session.commit() store.zipFolder(scenarioFolder + '.zip', scenarioFolder) # Redirect redirect(url('scenario', id=scenario.id))
# Import pylons modules from pylons import config # Import system modules import glob import os # Import custom modules import script_process from np import model from np.model import Session # Connect configuration = script_process.connect() config['storage_path'] = configuration.get('app:main', 'storage_path') # For each scenario, for scenario in Session.query(model.Scenario).order_by(model.Scenario.id): print scenario.getFolder(), scenarioInput = scenario.input scenario.input = None Session.commit() try: metricConfiguration = scenarioInput['metric configuration'] valueByName = metricConfiguration['demand (productive)'] try: value = valueByName['productive unit demand'] del valueByName['productive unit demand'] valueByName['productive unit demand per household per year'] = value scenario.input = scenarioInput print 'Changed parameter name' except KeyError: scenarioInput['demographic file name'] = os.path.basename(glob.glob(os.path.join(scenario.getFolder(), 'demographics.*'))[0])
def show(self, id, format='html'): 'GET /scenarios/id: Show a specific item' # If the output format is not supported, if format not in ['html', 'zip', 'geojson', 'json']: return 'Unsupported output format: ' + format try: id = int(id) except ValueError: return redirect(url('scenarios')) # Load personID = h.getPersonID() c.scenario = Session.query(model.Scenario).filter(model.Scenario.id==id).filter(model.getScopeFilter(personID)).first() # If user does not have access to the scenario, if not c.scenario: c.status = model.statusFailed if format == 'html': return render('/scenarios/show.mako') elif format == 'zip': return '' elif format == 'geojson': return geojson.dumps(geojson.FeatureCollection([])) elif format == 'json': return cjson.encode({}) # If the scenario has an error, if c.scenario.status == model.statusFailed: c.traceback = c.scenario.output['traceback'] c.status = model.statusFailed if format == 'html': return render('/scenarios/show.mako') elif format == 'zip': return forward(FileApp(c.scenario.getFolder() + '.zip')) elif format == 'geojson': return geojson.dumps(geojson.FeatureCollection([])) elif format == 'json': return c.scenario.exportJSON() # If the scenario has not been processed, if c.scenario.isQueued(): c.status = model.statusPending if format == 'html': return render('/scenarios/show.mako') elif format == 'zip': return forward(FileApp(c.scenario.getFolder() + '.zip')) elif format == 'geojson': return geojson.dumps(geojson.FeatureCollection([])) elif format == 'json': return c.scenario.exportJSON() # Prepare c.status = model.statusDone c.scenarioInput = c.scenario.input c.scenarioOutput = c.scenario.output transform_point = geometry_store.get_transform_point(geometry_store.proj4LL, geometry_store.proj4SM) # If the user wants HTML, if format == 'html': # Render scenario c.metricModel = metric.getModel(c.scenarioInput['metric model name']) scenarioStatistics = c.scenarioOutput['statistics'] nodeStatistics = scenarioStatistics['node'] # Prepare map centerX, centerY = transform_point(nodeStatistics['mean longitude'], nodeStatistics['mean latitude']) box1X, box1Y = transform_point(nodeStatistics['minimum longitude'], nodeStatistics['maximum latitude']) box2X, box2Y = transform_point(nodeStatistics['maximum longitude'], nodeStatistics['minimum latitude']) # Render map datasetStore = c.scenario.getDataset() c.mapFeatures = datasetStore.exportGeoJSON(transform_point) c.mapCenter = '%s, %s' % (centerX, centerY) c.mapBox = '%s, %s, %s, %s' % (box1X, box1Y, box2X, box2Y) # Render nodes c.nodes = list(datasetStore.cycleNodes()) c.populationQuartiles = scenarioStatistics['metric']['population quartiles'] # Render scenarios c.scenarios = Session.query(model.Scenario).filter(model.getScopeFilter(personID)).filter(model.Scenario.status==model.statusDone).filter(model.Scenario.id!=c.scenario.id).order_by(model.Scenario.id.desc()).all() # Return return render('/scenarios/show.mako') elif format == 'zip': return forward(FileApp(c.scenario.getFolder() + '.zip')) elif format == 'geojson': return c.scenario.getDataset().exportGeoJSON(transform_point) elif format == 'json': return c.scenario.exportJSON(request.params.get('nodeID'))
#!/usr/bin/env python 'Requeue pending scenarios' # Import custom modules import script_process from np import model from np.model import Session # If we are running the command as a script, if __name__ == '__main__': # Connect script_process.connect() # List for processorIP, processorWhen in Session.query(model.Processor.ip, model.Processor.when_updated).order_by(model.Processor.when_updated): print '{}\t{}'.format(processorIP, processorWhen.strftime('%Y%m%d %H:%M')) # Commit Session.commit()
sys.exit(1) # Setup a signal handler to exit gracefully upon interrupt # If called during processing of a scenario, that scenario # will be put into a Failed state def interrupt_handler(signum, frame): sys.exit(1) signal.signal(signal.SIGINT, interrupt_handler) # Prepare config['storage_path'] = configuration.get('app:main', 'storage_path') safe = environment.loadSafe(configuration.get('app:main', 'safe_path')) # Ping central server urllib.urlopen(safe['web']['url'] + '/processors/update') # For each new scenario, for scenario in Session.query(model.Scenario).filter(model.Scenario.status==model.statusNew): # Start log entry for this scenario Job.log("Start Scenario id %s" % scenario.id) # Mark scenario as pending scenario.status = model.statusPending Session.commit() try: # Run scenario.run() scenario.status = model.statusDone Job.log("End Scenario id %s" % scenario.id) except SystemExit: # Store traceback scenario.output = dict(traceback=''.join(traceback.format_exception(*sys.exc_info()))) scenario.status = model.statusFailed Job.log("Kill Scenario id %s" % scenario.id)
def index(self): "Show information about people registered in the database" c.people = Session.query(model.Person).all() return render("/people/index.mako")
def changeAccount(valueByName, action, templatePath, person=None): "Validate values and send confirmation email if values are okay" try: # Validate form form = PersonForm().to_python(valueByName, person) except formencode.Invalid, error: return dict(isOk=0, errorByID=error.unpack_errors()) else: # Purge expired candidates purgeExpiredPersonCandidates() # Prepare candidate candidate = model.PersonCandidate( form["username"], model.hashString(form["password"]), form["nickname"], form["email"], form["email_sms"] ) candidate.person_id = person.id if person else None candidate.ticket = store.makeRandomUniqueTicket(parameter.TICKET_LENGTH, Session.query(model.PersonCandidate)) candidate.when_expired = datetime.datetime.utcnow() + datetime.timedelta(days=parameter.TICKET_LIFESPAN_IN_DAYS) Session.add(candidate) Session.commit() # Prepare recipient toByValue = dict(nickname=form["nickname"], email=form["email"]) # Prepare subject subject = "[%s] Confirm %s" % (parameter.SITE_NAME, action) # Prepare body c.candidate = candidate c.username = form["username"] c.action = action body = render(templatePath) # Send try: smtp.sendMessage(config["safe"]["mail support"], toByValue, subject, body)
cursor = connection.cursor() # Create new database Base.metadata.bind = Session.bind Base.metadata.reflect() Base.metadata.drop_all() Base.metadata.create_all() # Migrate people cursor.execute('SELECT username, password_hash, nickname, email, email_sms, minutes_offset, rejection_count, pickled FROM people') for username, password_hash, nickname, email, email_sms, minutes_offset, rejection_count, pickled in cursor.fetchall(): person = model.Person(username, password_hash, nickname, email, email_sms) person.minutes_offset = minutes_offset person.rejection_count = rejection_count person.pickled = pickled Session.add(person) Session.commit() personByUsername = dict((x.username, x) for x in Session.query(model.Person)) # Migrate scenarios cursor.execute('SELECT scenarios.id, username, name, scope, when_created, input FROM scenarios INNER JOIN people ON scenarios.owner_id=people.id WHERE status=?', [model.statusDone]) for scenarioID, username, name, scope, when_created, input in cursor.fetchall(): scenario = model.Scenario(personByUsername[username].id, name, scope) scenario.when_created = datetime.datetime.strptime(when_created, '%Y-%m-%d %H:%M:%S.%f') scenarioInput = pickle.loads(str(input)) existingNetworkPath = scenarioInput['network configuration']['network']['existing network path'] del scenarioInput['network configuration']['network']['existing network path'] if existingNetworkPath: scenarioInput['network configuration']['network']['existing networks'] = u'network/network/existing networks.zip' scenario.input = scenarioInput Session.add(scenario) Session.commit()
except Exception as e: print("Error evaluating input dict: %s" % e) sys.exit(-1) else: print("required params: -c <environment>.ini \"<input_dict_as_python>\"") sys.exit(-1) # get ids from stdin into a list ids = [] for id in sys.stdin: ids.append(int(id)) config['storage_path'] = configuration.get('app:main', 'storage_path') storagePath = config['storage_path'] # Iterate through scenarios scenarios = Session.query(model.Scenario).\ filter(model.Scenario.id.in_(ids)).\ order_by(model.Scenario.id) for scenario in scenarios: #update the input and commit it scenInput = scenario.input scenario.input = None Session.commit() util.update(scenInput, new_input) scenario.input = scenInput Session.commit() print("updated input of scenario id: %s" % scenario.id)
""" Save scenario leve inputs """ scenarioFolder = scenario.getFolder() store.makeFolderSafely(scenarioFolder) expandPath = lambda x: os.path.join(scenarioFolder, x) metricConfiguration = scenario.input['metric configuration'] metric.saveMetricsConfigurationCSV(expandPath('metrics-job-input'), metricConfiguration) store.zipFolder(scenarioFolder + '.zip', scenarioFolder) # If the user is running the script from the command-line, if __name__ == '__main__': # Connect (get config and setup model from appropriate DB) configuration = script_process.connect() config['storage_path'] = configuration.get('app:main', 'storage_path') # get ids from stdin into a list ids = sys.stdin.readlines() # Iterate through scenarios saving input files scenarios = Session.query(model.Scenario).\ filter(and_(model.Scenario.id.in_(ids), (model.Scenario.status == model.statusDone))).\ order_by(model.Scenario.id) for scenario in scenarios: print "saving metric inputs for scenario id: %s" % scenario.id save_job_input_file(scenario)
def index(self): 'Show 10 most recent jobs' c.jobs = Session.query(model.Job).order_by(model.Job.start_time.desc()).limit(10).all() return render('/jobs/index.mako')