def begin_new_trial(self): # save username and trial time to db sql = (""" INSERT INTO landmark_experiment (username, trial_time) VALUES (%s, now()) """) % (self.req.db.quoted(self.req.client.username), ) self.req.db.transaction_begin_rw() self.req.db.sql(sql) self.req.db.transaction_commit() # choose 5 random routes from list routes = random.sample(conf.landmarks_exp_rt_system_ids, Landmark.experiment_count) # save routes to db for sys_id in routes: sql = (""" INSERT INTO landmark_exp_route (username, route_system_id, last_modified) VALUES (%s, %d, now()) """) % ( self.req.db.quoted(self.req.client.username), sys_id, ) self.req.db.transaction_begin_rw() self.req.db.sql(sql) self.req.db.transaction_commit() # send route to client route_xml = etree.Element('route') misc.xa_set(route_xml, 'route_system_id', sys_id) self.xml.append(route_xml)
def get_existing_trial(self): # Check that the user hasn't completed the experiment previously. sql = (""" SELECT COUNT(feedback) FROM landmark_exp_feedback AS r WHERE r.username = %s """ % (self.req.db.quoted(self.req.client.username), )) rows = self.req.db.sql(sql) g.assurt(len(rows) == 1) if rows[0]['count'] == 0: # find out which routes this user has not finished sql = (""" SELECT route_system_id FROM landmark_exp_route AS r WHERE r.username = %s AND NOT r.done """ % (self.req.db.quoted(self.req.client.username), )) rows = self.req.db.sql(sql) # send list of routes to client for row in rows: route_xml = etree.Element('route') misc.xa_set(route_xml, 'route_system_id', row['route_system_id']) self.xml.append(route_xml)
def prepare_response(self): results_xml = etree.Element('results') misc.xa_set(results_xml, 'ftxt', self.req.filters.filter_by_text_smart) # NOTE: revision_get does this instead of as_xml: # self.results = qb.db.table_to_dom('revision', grevs_sql_all) for res in self.results: results_xml.append(res.as_xml()) self.doc.append(results_xml)
def prepare_resp_doc(self, the_doc, item_type): sub_doc = etree.Element('items') misc.xa_set(sub_doc, 'ityp', item_type) if self.grand_total is not None: # Tell the user the complete number of items that match their query. misc.xa_set(sub_doc, 'grand_total', self.grand_total) the_doc.append(sub_doc) return sub_doc
def as_xml(self, elem_name='gwis_error'): doc = etree.Element(elem_name) misc.xa_set(doc, 'msg', str(self)) if self.tag is not None: misc.xa_set(doc, 'tag', self.tag) for gerr in self.gerrs: gerr.append_gml(doc, False) xml = etree.tostring(doc) return xml
def attrs_to_xml(self, elem, need_digest, extra_attrs=None, include_input_only_attrs=False): '''Add my attributes named in attrs as XML attributes of element elem.''' # The route finder uses extra_attrs, e.g., # [b'bonus_tagged', b'penalty_tagged', b'rating'] if need_digest: md5sum = hashlib.md5() # Only send attributes whose definition says it should be sent to client. if not include_input_only_attrs: attrs = self.gwis_defns[0] else: attrs = self.gwis_defns[1] if extra_attrs: for extra_attr in extra_attrs: attrs += (extra_attr, extra_attr,) #log.debug('attrs_to_xml: attrs: %s' % (str(attrs),)) # EXPLAIN: Not sure why we sort here.... #for attr in sorted(attrs): for attr in attrs: #log.debug(' >> attr: %s / %s' % (attr, getattr(self, attr),)) attr_val = getattr(self, attr[0], None) if attr_val is not None: misc.xa_set(elem, attr[1], attr_val) # FIXME: This code is probably obsolete, since Diff now does the checking when # the byways are checked out. # If we need a digest, we build an md5 hash based on the # non-geometric properties of the item. The digest is used when # diffing in the client to quickly tell if anything has changed. # - Certain property changes are meaningless, so we exclude them # FIXME I think this is broken... needs to be tested... # (okay i no longer think it's broken) # FIXME Move some of these to geofeature.py and make a class array # MAGIC_NUMBER: 0 is col_tuple[One.scol_pyname] if (need_digest and attr[0] not in ( #'system_id', #'branch_id', 'stack_id', 'version', 'geometry_len', 'beg_node_id', 'fin_node_id', # I missing a bunch of generated cols that probably should be excluded... 'node_lhs_elevation_m', 'node_rhs_elevation_m', 'split_from_stack_id',)): md5sum.update(attr[0] + str(getattr(self, attr[0]))) if (need_digest): # NOTE The GML elem 'dng' is called digest_nongeo in flashclient # FIXME Rename to rev_digest? misc.xa_set(elem, 'dng', md5sum.hexdigest())
def prepare_response(self): e = etree.Element('access_control') misc.xa_set(e, 'control_type', self.control_type) self.doc.append(e) # FIXME Does need_digest apply to control access records? Maybe just to # group_membership, which is revisioned. (This is what checkout # does) need_digest = isinstance(self.req.revision.rev, revision.Diff) # Add the results from the SQL query to our XML document. self.control_handler.append_gml(e, need_digest)
def fetch_n_save(self): command.Op_Handler.fetch_n_save(self) self.xml = etree.Element('lmrk_exp') misc.xa_set(self.xml, 'active', conf.landmarks_experiment_active) if ((conf.landmarks_experiment_active) and (self.req.client.username) and (self.req.client.username != conf.anonymous_username)): self.get_existing_trial_route_count_remaining()
def fetch_n_save(self): command.Op_Handler.fetch_n_save(self) self.xml = etree.Element('lmrk_trial') if (not conf.landmarks_experiment_active): misc.xa_set(self.xml, 'cond', 'exp-off') else: if ((not self.req.client.username) or (self.req.client.username == conf.anonymous_username)): misc.xa_set(self.xml, 'cond', 'no-usr') elif (self.trial_num is not None): self.get_previous_trial() else: self.set_new_trial()
def as_xml(self): bans = etree.Element('bans') misc.xa_set(bans, 'public_user', self.public_ban_user) misc.xa_set(bans, 'full_user', self.full_ban_user) misc.xa_set(bans, 'public_ip', self.public_ban_ip) misc.xa_set(bans, 'full_ip', self.full_ban_ip) return bans
def postpare_response(self, doc, elem, extras): # gwis.command_.checkout sets up the XML document. We add a few # attributes to the <items> doc and make new ones for the link_posts. g.assurt(self.owning_thread is not None) # Add a few thread details to the XML doc. misc.xa_set(elem, 'thread_stack_id', self.owning_thread.stack_id) misc.xa_set(elem, 'thread_title', self.owning_thread.name) misc.xa_set(elem, 'ttype', self.owning_thread.ttype) misc.xa_set(elem, 'thread_type_id', self.owning_thread.thread_type_id) # Add the link_values and their geometries. #doc.append(self.result) # NOTE: Users can link some geofeature types to posts, but not all types. # We currently don't support branches, routes, or terrain. for link_posts in ( self.linked_byways, self.linked_regions, self.linked_waypoints, self.linked_revisions, self.linked_routes, ): if link_posts: # MAGIC_NUMBER: 'link_post' sub_doc = self.prepare_resp_doc(doc, 'link_post') link_posts.append_gml(sub_doc, need_digest=False) if self.reac_data: doc.append(self.reac_data)
def postpare_response(self, doc, elem, extras): # gwis.command_.checkout sets up the XML document. We add a few # attributes to the <items> doc and make new ones for the link_posts. g.assurt(self.owning_thread is not None) # Add a few thread details to the XML doc. misc.xa_set(elem, 'thread_stack_id', self.owning_thread.stack_id) misc.xa_set(elem, 'thread_title', self.owning_thread.name) misc.xa_set(elem, 'ttype', self.owning_thread.ttype) misc.xa_set(elem, 'thread_type_id', self.owning_thread.thread_type_id) # Add the link_values and their geometries. #doc.append(self.result) # NOTE: Users can link some geofeature types to posts, but not all types. # We currently don't support branches, routes, or terrain. for link_posts in (self.linked_byways, self.linked_regions, self.linked_waypoints, self.linked_revisions, self.linked_routes,): if link_posts: # MAGIC_NUMBER: 'link_post' sub_doc = self.prepare_resp_doc(doc, 'link_post') link_posts.append_gml(sub_doc, need_digest=False) if self.reac_data: doc.append(self.reac_data);
def get_previous_trial(self): # We need the track id and the landmark prompt locations sql = (""" SELECT track_id, condition FROM landmark_trial AS t WHERE t.username = %s AND t.trial_num = %s """ % ( self.req.db.quoted(self.req.client.username), self.trial_num, )) rows = self.req.db.sql(sql) misc.xa_set(self.xml, 'tid', rows[0]['track_id']) misc.xa_set(self.xml, 'cond', rows[0]['condition']) sql = (""" SELECT n.node_stack_id, ST_AsText(n.endpoint_xy) as geo FROM landmark_prompt AS p JOIN node_endpt_xy as n ON (p.node_id = n.node_stack_id) WHERE p.username = %s AND p.trial_num = %s """ % ( self.req.db.quoted(self.req.client.username), self.trial_num, )) rows = self.req.db.sql(sql) for row in rows: prompts_xml = etree.Element('prompt') misc.xa_set(prompts_xml, 'nid', row['node_stack_id']) misc.xa_set(prompts_xml, 'geometry', row['geo'][6:-1]) self.xml.append(prompts_xml)
def append_gpx(self, db, elem, step_number): # NOTE: we must re-query the geometry so that we can transform it into # WGS84 for the gpx spec # FIXME: EXPLAIN: Do we care about permissions here? # FIXME: If we know we're exporting to GPX, can't we do this on fetch? if self.travel_mode == Travel_Mode.bicycle: rows = db.sql(""" SELECT ST_AsText(ST_Transform(geometry, %d)) as geometry FROM geofeature WHERE system_id = %d """ % ( conf.srid_latlon, self.byway_id, )) else: g.assurt(self.step_number) rows = db.sql(""" SELECT ST_AsText(ST_Transform(transit_geometry, %d)) as geometry FROM route_step WHERE route_id = %d AND step_number = %d """ % ( conf.srid_latlon, self.route_id, self.step_number, )) wgs_xy = geometry.wkt_line_to_xy(rows[0]['geometry']) if not self.forward: wgs_xy.reverse() for lonlat in wgs_xy: # Parsed pair is [longitude, latitude] # FIXME: Search this element name. Why 'trk'? Because GPX? new = etree.Element('trkpt') misc.xa_set(new, 'lat', lonlat[1]) misc.xa_set(new, 'lon', lonlat[0]) name = etree.SubElement(new, 'name') name.text = self.step_name elem.append(new)
def prepare_response(self): if self.rid_count is not None: # Tell the user the complete number of revision that match their query misc.xa_set(self.results, 'total', self.rid_count) if self.req.filters.include_geosummary: # Remove each geosummary attribute and install in its place a # geosummary child element with a properly formatted coordinate list. for row in self.results: if row.get('geosummary'): g.assurt(row.get('geosummary')[:2] == 'M ') geosummary_child = etree.Element('geosummary') gml.append_MultiPolygon(geosummary_child, row.get('geosummary')) del row.attrib['geosummary'] row.append(geosummary_child) self.doc.append(self.results)
def prepare_response(self): g.assurt(self.req.client.username != conf.anonymous_username) # Skipping: BUG 2688: No need to use transaction_retryable, # since user_token_gen does it own retrying. e = etree.Element('token') e.text = self.req.client.user_token_generate(self.req.client.username) self.doc.append(e) # FIXME: Bug nnnn: Generate the session ID / sessid here, too... e = etree.Element('preferences') p = self.preferences_get() for (col, value) in p.items(): misc.xa_set(e, col, value) self.doc.append(e)
def prepare_metaresp(self): command_base.Op_Handler.prepare_metaresp(self) # MAYBE: Like cp_maint_beg and cp_maint_fin, we could have the client # ask for 'semiprotect', since flashclient should only need it if # the user is editing (it restricts new and anonymous users from # editing but not from getting routes). See: kval_get.py. misc.xa_set(self.doc, 'semiprotect', self.semiprotect_wait()) if self.user_client_ban.is_banned(): self.doc.append(self.user_client_ban.as_xml()) # Check if the user's email address is invalid # NOTE: Flashclient will show a popup to tell the user if # their email address is marked bouncing. if self.req.client.username != conf.anonymous_username: if (user_email.flag_get(self.req.db, self.req.client.username, 'bouncing')): misc.xa_set( self.doc, 'bouncing', user_email.addr_get(self.req.db, self.req.client.username, False))
def fetch_n_save(self): command.Op_Handler.fetch_n_save(self) self.xml = etree.Element('lmrk_need') if (conf.landmarks_experiment_active): sql = (""" SELECT distinct n.stack_id, ST_AsText(nxy.endpoint_xy) as geo, (SELECT count(*) FROM geofeature JOIN group_item_access as gia ON (geofeature.stack_id = gia.stack_id AND geofeature.version = gia.version) WHERE geofeature_layer_id=103 AND ST_Distance(nxy.endpoint_xy, geofeature.geometry) < 50 AND NOT deleted AND valid_until_rid = %d AND gia.branch_id = %d) as points_nearby FROM node_endpoint as n JOIN node_endpt_xy as nxy ON (n.stack_id = nxy.node_stack_id) WHERE ST_Distance(nxy.endpoint_xy, ST_SetSRID(ST_Point(%s, %s), %d)) < 500 AND reference_n > 2 """ % ( conf.rid_inf, branch.Many.baseline_id(self.req.db), self.x, self.y, conf.default_srid, )) # TODO: get only stuff in current branch rows = self.req.db.sql(sql) for row in rows: if (row['points_nearby'] == 0): need_xml = etree.Element('need') misc.xa_set(need_xml, 'nid', row['stack_id']) misc.xa_set(need_xml, 'geometry', row['geo'][6:-1]) self.xml.append(need_xml)
def prepare_metaresp(self): command_base.Op_Handler.prepare_metaresp(self) # MAYBE: Like cp_maint_beg and cp_maint_fin, we could have the client # ask for 'semiprotect', since flashclient should only need it if # the user is editing (it restricts new and anonymous users from # editing but not from getting routes). See: kval_get.py. misc.xa_set(self.doc, 'semiprotect', self.semiprotect_wait()) if self.user_client_ban.is_banned(): self.doc.append(self.user_client_ban.as_xml()) # Check if the user's email address is invalid # NOTE: Flashclient will show a popup to tell the user if # their email address is marked bouncing. if self.req.client.username != conf.anonymous_username: if (user_email.flag_get( self.req.db, self.req.client.username, 'bouncing')): misc.xa_set( self.doc, 'bouncing', user_email.addr_get( self.req.db, self.req.client.username, False))
def get_trial(self): # get routes from pt 1 routes_p1 = list() sql = ( """ SELECT route_system_id FROM landmark_exp_route AS r WHERE r.username = %s AND part = 1 """ % (self.req.db.quoted(self.req.client.username),)) rows = self.req.db.sql(sql) for row in rows: routes_p1.append(row['route_system_id']) # get pt 2 routes sql = ( """ SELECT u.route_system_id, u.route_user_id, u.done FROM landmark_exp_route AS r LEFT JOIN landmark_exp_route_p2_users as u ON (u.username = r.username AND u.route_system_id = r.route_system_id) WHERE r.username = %s AND part = 2 """ % (self.req.db.quoted(self.req.client.username),)) rows = self.req.db.sql(sql) routes_p2 = list() # if list is empty, generate new routes if len(rows) == 0: routes_p2 = self.generate_new_routes(routes_p1) else: # keep only new routes for r in rows: if (not r['done']): routes_p2.append((r['route_system_id'], r['route_user_id'],)) # send list of routes to client for r_id in routes_p1: route_xml = etree.Element('route_p1') misc.xa_set(route_xml, 'route_system_id', r_id) self.xml.append(route_xml) for rt in routes_p2: route_xml = etree.Element('route_p2') misc.xa_set(route_xml, 'route_system_id', rt[0]) misc.xa_set(route_xml, 'route_user_id', rt[1]) self.xml.append(route_xml)
def prepare_metaresp(self): 'Another fetch cycle for things best done after saving.' # SYNC_ME: Search fetch doc metadata. #log.debug('prepare_metaresp: VERSION.major: %s' % (VERSION.major,)) misc.xa_set(self.doc, 'major', VERSION.major) misc.xa_set(self.doc, 'gwis_version', conf.gwis_version) # BUG nnnn: How best to handle updating the user's working revision. # For now, we always send the rid_max, because of how # flashclient is programmed (which is to reset rev_viewport # and make it again when the first GWIS response is received). # MAYBE/BUG nnnn: Flashclient and Android should ping periodically for # the revision ID, e.g., maybe via key_value_get.py. rid_max = str(revision.Revision.revision_max(self.req.db)) misc.xa_set(self.doc, 'rid_max', rid_max)
def get_trial(self): # get routes from pt 1 routes_p1 = list() sql = (""" SELECT route_system_id FROM landmark_exp_route AS r WHERE r.username = %s AND part = 1 """ % (self.req.db.quoted(self.req.client.username), )) rows = self.req.db.sql(sql) for row in rows: routes_p1.append(row['route_system_id']) # get pt 2 routes sql = (""" SELECT u.route_system_id, u.route_user_id, u.done FROM landmark_exp_route AS r LEFT JOIN landmark_exp_route_p2_users as u ON (u.username = r.username AND u.route_system_id = r.route_system_id) WHERE r.username = %s AND part = 2 """ % (self.req.db.quoted(self.req.client.username), )) rows = self.req.db.sql(sql) routes_p2 = list() # if list is empty, generate new routes if len(rows) == 0: routes_p2 = self.generate_new_routes(routes_p1) else: # keep only new routes for r in rows: if (not r['done']): routes_p2.append(( r['route_system_id'], r['route_user_id'], )) # send list of routes to client for r_id in routes_p1: route_xml = etree.Element('route_p1') misc.xa_set(route_xml, 'route_system_id', r_id) self.xml.append(route_xml) for rt in routes_p2: route_xml = etree.Element('route_p2') misc.xa_set(route_xml, 'route_system_id', rt[0]) misc.xa_set(route_xml, 'route_user_id', rt[1]) self.xml.append(route_xml)
def append_gml(self, elem, need_digest, new=None, extra_attrs=None, include_input_only_attrs=False): # We're called by item_base.Many for each record it found. It created # a master XML document, and it wants us to create a child document to # contain the geofeature. g.assurt(not new) if new is None: # NOTE This code CxPx from item_versioned.append_gml g.assurt(self.item_type_id != '') new = etree.Element(Item_Type.id_to_str(self.item_type_id)) if need_digest: # NOTE The GML elem 'dg' is the _d_igest for _g_eometry # EXPLAIN: How is the digest used? # FIXME: Can I use any of the geometries?? Probably not... g.assurt(self.geometry_svg) # MAYBE: Depending on the geometry representation, it's a diff digest. geometry = self.geometry_svg or self.geometry_wkt # or self.geometry_raw misc.xa_set(new, 'dg', hashlib.md5(geometry).hexdigest()) if self.attrs: attrs = etree.Element('attrs') for attr_name, attr_val in self.attrs.iteritems(): attr = etree.Element('a') misc.xa_set(attr, 'k', attr_name) # FIXME: Does this value need to be encoded? # Test with </t> in a tag name. or <t> misc.xa_set(attr, 'v', attr_val) attrs.append(attr) new.append(attrs) if self.tagged: # We can't just use a comma-separated list because some tags include # commas. It's easiest just to make another subdocument. # NO: misc.xa_set(new, 'tags', ', '.join(self.tagged)) tags = etree.Element('tags') for tag_name in self.tagged: tag = etree.Element('t') tag.text = tag_name tags.append(tag) new.append(tags) self.append_gml_geometry(new) return item_user_watching.One.append_gml(self, elem, need_digest, new, extra_attrs, include_input_only_attrs)
def get_existing_trial_route_count_remaining(self): # Send count of remaining routes and completed routes to client. route_xml = etree.Element('route') sql = ( """ SELECT COUNT(route_system_id) AS count FROM landmark_exp_route AS r WHERE r.username = %s AND NOT r.done AND part = %d """ % (self.req.db.quoted(self.req.client.username), self.experiment_part,)) rows = self.req.db.sql(sql) g.assurt(len(rows) == 1) misc.xa_set(route_xml, 'routes_togo', rows[0]['count']) self.xml.append(route_xml) sql = ( """ SELECT COUNT(route_system_id) AS count FROM landmark_exp_route AS r WHERE r.username = %s AND r.done AND part = %d """ % (self.req.db.quoted(self.req.client.username), self.experiment_part,)) rows = self.req.db.sql(sql) g.assurt(len(rows) == 1) misc.xa_set(route_xml, 'routes_done', rows[0]['count']) self.xml.append(route_xml) sql = ( """ SELECT COUNT(feedback) FROM landmark_exp_feedback AS r WHERE r.username = %s """ % (self.req.db.quoted(self.req.client.username),)) rows = self.req.db.sql(sql) g.assurt(len(rows) == 1) misc.xa_set(route_xml, 'user_done', rows[0]['count']) self.xml.append(route_xml)
def search_calculate_reactions(self, qb): # BUG_FALL_2013: Delete this fcn. # See polarity_sql = Many.sql_polarity in thread.py: [lb] thinks this # fcn. can be deleted. We calculate the likes and dislikes of a thread, # but it doesn't make sense to count it for just one post... especially # since a post with polarity has no comment, so it doesn't make sense # that we'd hydrate such a post. And posts with comments have no polarity # so the likes and dislikes will be zero... g.assurt(False) # FIXME: Delete this fcn. and the commented-out code # above that calls it. g.assurt(self.owning_thread is not None) if (self.owning_thread.thread_type_id == Thread_Type.reaction): # SELECT polarity, body FROM post WHERE body IS NULL; # ==> polarity is 1 or -1, body is never set. # SELECT polarity, body FROM post WHERE body IS NOT NULL; # ==> polarityis 0 and body is set. # MAYBE: Does this sql take a while? time_0 = time.time() rsql = thread.Many.sql_polarity(qb, self.owning_thread.stack_id) rres = qb.db.sql(rsql) self.reac_data = etree.Element('reac_data') misc.xa_set(self.reac_data, 'likes', rres[0]['likes']) misc.xa_set(self.reac_data, 'dislikes', rres[0]['dislikes']) misc.xa_set(self.reac_data, 'comments', rres[0]['comments']) log.debug( 'srch_calc_reacts: likes: %d / disls: %d / cmmnts: %d / %s' % ( rres[0]['likes'], rres[0]['dislikes'], rres[0]['comments'], misc.time_format_elapsed(time_0), ))
def search_calculate_reactions(self, qb): # BUG_FALL_2013: Delete this fcn. # See polarity_sql = Many.sql_polarity in thread.py: [lb] thinks this # fcn. can be deleted. We calculate the likes and dislikes of a thread, # but it doesn't make sense to count it for just one post... especially # since a post with polarity has no comment, so it doesn't make sense # that we'd hydrate such a post. And posts with comments have no polarity # so the likes and dislikes will be zero... g.assurt(False) # FIXME: Delete this fcn. and the commented-out code # above that calls it. g.assurt(self.owning_thread is not None) if (self.owning_thread.thread_type_id == Thread_Type.reaction): # SELECT polarity, body FROM post WHERE body IS NULL; # ==> polarity is 1 or -1, body is never set. # SELECT polarity, body FROM post WHERE body IS NOT NULL; # ==> polarityis 0 and body is set. # MAYBE: Does this sql take a while? time_0 = time.time() rsql = thread.Many.sql_polarity(qb, self.owning_thread.stack_id) rres = qb.db.sql(rsql) self.reac_data = etree.Element('reac_data') misc.xa_set(self.reac_data, 'likes', rres[0]['likes']) misc.xa_set(self.reac_data, 'dislikes', rres[0]['dislikes']) misc.xa_set(self.reac_data, 'comments', rres[0]['comments']) log.debug( 'srch_calc_reacts: likes: %d / disls: %d / cmmnts: %d / %s' % (rres[0]['likes'], rres[0]['dislikes'], rres[0]['comments'], misc.time_format_elapsed(time_0),))
def append_gml(self, elem): new = et.Element(Item_Type.id_to_str(self.item_type_id)) misc.xa_set(new, 'name', self.name) misc.xa_set(new, 'item_id', self.item_id) misc.xa_set(new, 'type_id', self.type_id) misc.xa_set(new, 'step', self.step_number) misc.xa_set(new, 'dist', self.dist) misc.xa_set(new, 'disp', self.display) if self.geometry: if self.type_id == Item_Type.WAYPOINT: gml.append_Point(new, self.geometry) else: gml.append_Polygon(new, self.geometry) elem.append(new)
def fetch_n_save(self): command.Op_Handler.fetch_n_save(self) qb = self.req.as_iqb() for addr in self.addrs: addr_qb = None try: # Start with fresh qb for each addr, so that qb_filters is # fresh and so none of the temp tables pre-exist. addr_qb = qb.clone(skip_clauses=True, skip_filtport=True, db_get_new=True) (result_gps, hit_count_text,) = self.addr_geocode(addr_qb, addr) finally: if (addr_qb is not None) and (addr_qb.db is not None): addr_qb.db.close() addr_qb.db = None # FIXME Better, I [reid] think, to _not_ throw an exception and let # flashclient deal with it (perhaps in Address_Choose.mxml)? # But this is fine for now. self.raise_error_if_no_results(addr, result_gps) # # FIXME: This used to use Address().as_xml(), but the results from # addr_geocode are search results now. So maybe implement # gp_res.as_xml_addr() addr_xml = etree.Element('addr') # BUG nnnn: Android does not encode properly, e.g., raw '&'s in XML. # [lb] tried to work around the android bug by converting raw '&'s, # but android expects the addr in our response to match the addr # in the query... so we'd have to replace our corrected addr # query with the original, incorrectly (un)encoded request here. # If not (as the code is here, now) if we change the addr by # fixing it, android will ignore the response and will send # another request. And the cycle repeats itself.... misc.xa_set(addr_xml, 'text', addr) # HACK: hit_count_text is used by the route finder to complain to the # user if too many userpoints of the same name were found. misc.xa_set(addr_xml, 'hit_count_text', hit_count_text) for gp_res in result_gps: # See the class: Search_Result_Geofeature. gf_res = gp_res.result_gfs[0] # SYNC_ME: gwis.command_.geocode.fetch_n_save # and item.util.address.as_xml res_xml = etree.Element('addr') # From Search_Result_Group: misc.xa_set(res_xml, 'text', gp_res.gf_name) misc.xa_set(res_xml, 'gc_id', gp_res.gc_fulfiller) misc.xa_set(res_xml, 'gc_ego', gp_res.gc_confidence) # From Search_Result_Geofeature: misc.xa_set(res_xml, 'x', gf_res.x) misc.xa_set(res_xml, 'y', gf_res.y) misc.xa_set(res_xml, 'width', gf_res.width) misc.xa_set(res_xml, 'height', gf_res.height) # addr_xml.append(res_xml) self.doc.append(addr_xml)
def as_xml(self): elem = etree.Element('result') misc.xa_set(elem, 'gf_name', self.gf_name) misc.xa_set(elem, 'gf_type_id', self.gf_type_id) # Skipping (since some are addys... or maybe that doesn't matter): # misc.xa_set(elem, 'gf_stack_id', self.gf_stack_id) for result in self.result_gfs: obj = etree.Element('gf_item') misc.xa_set(obj, 'stack_id', result.stack_id) if result.geometry: misc.xa_set(obj, 'geometry', result.geometry) # Skipping center, which we only use interally. misc.xa_set(obj, 'x', result.x) misc.xa_set(obj, 'y', result.y) # MAYBE: width and height... or no one cares? elem.append(obj) # obj_in = etree.Element('ts_in') obj_ex = etree.Element('ts_ex') for vect_name in search_full_text.Full_Text_Query_Part.ts_vects: misc.xa_set(obj_in, vect_name, self.ts_include[vect_name]) misc.xa_set(obj_ex, vect_name, self.ts_exclude[vect_name]) elem.append(obj_in) elem.append(obj_ex) # return elem
def fetch_n_save(self): command.Op_Handler.fetch_n_save(self) # Assemble the qb from the request. qb = self.req.as_iqb(addons=False) g.assurt(qb.filters == Query_Filters(None)) item_stack_ids = qb.filters.decode_ids_compact('./fbil_sids', self.req.doc_in) if ((not item_stack_ids) or (item_stack_ids.find(',') != -1)): raise GWIS_Error('Expecting one stack ID in fbil_sids doc.') items = item_user_access.Many() g.assurt(qb.sql_clauses is None) qb.sql_clauses = item_user_access.Many.sql_clauses_cols_all.clone() qb.filters.dont_load_feat_attcs = True qb.filters.only_stack_ids = item_stack_ids sql_many = items.search_get_sql(qb) sql_itmf = (""" SELECT itmf.item_stack_id , itmf.username -- , itmf.user_id , itmf.library_squelch , itmf.show_in_history -- , itmf.last_viewed -- , itmf.branch_id FROM (%s) AS gia JOIN item_findability AS itmf ON (gia.stack_id = itmf.item_stack_id) WHERE ( (itmf.username = %s) AND (gia.access_level_id <= %d)) OR ( (itmf.username = %s) AND (gia.access_level_id <= %d)) """ % ( sql_many, qb.db.quoted(conf.anonymous_username), Access_Level.arbiter, qb.db.quoted(qb.username), Access_Level.viewer, )) rows = qb.db.sql(sql_itmf) if len(rows) == 0: raise GWIS_Warning('No item_findability for user-item.') # We'll find one or two records: one for the user, and one for the # public. # EXPLAIN: What records exist for anon route request? # [lb] thinks it's a GIA record with a stealth secret, # but probably not an item_findability record. # # FIXME: TEST: anon get route, log in, save route # fbilities = etree.Element('fbilities') for row in rows: fbility = etree.Element('fbility') misc.xa_set(fbility, 'sid', row['item_stack_id']) if row['username'] == conf.anonymous_username: # MAGIC_NUMBER: Usernames prefixed with a floorbar are special. username_hacked = '_anonymous' else: username_hacked = row['username'] misc.xa_set(fbility, 'unom', username_hacked) #misc.xa_set(fbility, 'uid', row['user_id']) misc.xa_set(fbility, 'sqel', row['library_squelch']) misc.xa_set(fbility, 'hist', row['show_in_history']) #misc.xa_set(fbility, 'lstv', row['last_viewed']) #misc.xa_set(fbility, 'brid', row['branch_id']) fbilities.append(fbility) self.itmf_xml = fbilities
def set_new_trial(self): conditions = { 'now-low': 0, 'now-high': 0, 'later-low': 0, 'later-high': 0, } # get trial history (what condition and how many prompts) sql = (""" SELECT condition, count(*) AS ct FROM landmark_trial AS t JOIN landmark_prompt AS p ON (t.username = p.username AND t.trial_num = p.trial_num) WHERE t.username = %s AND track_id IS NOT NULL AND NOT track_id = -1 GROUP BY condition """ % (self.req.db.quoted(self.req.client.username), )) rows = self.req.db.sql(sql) # get highest trial number sql = (""" SELECT MAX(trial_num) AS num FROM landmark_trial AS t WHERE t.username = %s """ % (self.req.db.quoted(self.req.client.username), )) max_trial_num = -1 num_trial_rows = self.req.db.sql(sql) if (len(num_trial_rows) > 0): max_trial_num = num_trial_rows[0]['num'] if max_trial_num is None: max_trial_num = -1 new_trial_num = max_trial_num + 1 if (new_trial_num > 0): # Check whether the last trial was incomplete sql = (""" SELECT condition FROM landmark_trial AS t WHERE t.username = %s AND track_id = -1 AND trial_num = %d GROUP BY condition """ % ( self.req.db.quoted(self.req.client.username), max_trial_num, )) p_rows = self.req.db.sql(sql) if (len(p_rows) > 0 and p_rows[0]['condition'] is not None): # This will give priority to retrying previous incomplete condition conditions[(p_rows[0]['condition'] + '-low')] -= 100 conditions[(p_rows[0]['condition'] + '-high')] -= 100 if (len(rows) == 0): # if no history, get first trial conditions for all users sql = (""" SELECT t.username, condition, count(*) AS ct FROM landmark_trial AS t JOIN landmark_prompt AS p ON (t.username = p.username AND t.trial_num = p.trial_num) WHERE t.trial_num = 0 GROUP BY t.username, condition """) rows = self.req.db.sql(sql) # get counts for conditions and choose lowest (random if more than # one with the lowest count) for row in rows: if row['ct'] < 4: conditions[(row['condition'] + '-low')] += 1 else: conditions[(row['condition'] + '-high')] += 1 # decide on new conditions and save condition_keys = ['now-low', 'now-high', 'later-low', 'later-high'] min_conds = [condition_keys[0]] min_ct = conditions[min_conds[0]] for i in xrange(1, len(condition_keys)): if (conditions[condition_keys[i]] < min_ct): min_conds = [condition_keys[i]] min_ct = conditions[min_conds[0]] elif (conditions[condition_keys[i]] == min_ct): min_conds.append(condition_keys[i]) final_condition = 'none' if (len(min_conds) == 1): # we have our condition final_condition = min_conds[0] else: # We need to assing one randomly random.seed(time.time()) final_condition = min_conds[random.randint(0, len(min_conds) - 1)] misc.xa_set(self.xml, 'cond', final_condition) misc.xa_set(self.xml, 'trial_num', new_trial_num) # Save trial information sql = (""" INSERT INTO landmark_trial (username, trial_num, trial_time, condition, track_id, email_sent) VALUES (%s, %d, now(), '%s', %d, '%s') """) % ( self.req.db.quoted(self.req.client.username), new_trial_num, str.split(final_condition, '-')[0], -1, False, ) self.req.db.transaction_begin_rw() self.req.db.sql(sql) self.req.db.transaction_commit()
def fetch_n_save(self): command.Op_Handler.fetch_n_save(self) # Assemble the qb from the request. qb = self.req.as_iqb(addons=False) g.assurt(qb.filters == Query_Filters(None)) item_stack_ids = qb.filters.decode_ids_compact('./ssec_sids', self.req.doc_in) if ((not item_stack_ids) or (item_stack_ids.find(',') != -1)): raise GWIS_Error('Expecting one stack ID in ssec_sids doc.') item_stack_id = int(item_stack_ids) if item_stack_id <= 0: raise GWIS_Error('Expecting positive valued stack ID.') items_fetched = item_user_access.Many() # So we get access_infer, etc. qb.filters.include_item_stack = True # Don't forget to maybe use the session ID. qb.filters.gia_use_sessid = self.req.filters.gia_use_sessid items_fetched.search_by_stack_id(item_stack_id, qb) if not items_fetched: raise GWIS_Error( 'Item stack ID not found or you cannot access: %d' % (item_stack_id, )) g.assurt(len(items_fetched) == 1) item = items_fetched[0] log.verbose1('verify_access: fetched: %s' % (str(item), )) g.assurt(not item.fresh) g.assurt(not item.valid) # What's the policy for who can make stealth secret values? # They're not really that special, but if every item in the database # had one, that's a lotta mozzarrella. # # Users should at least be able to edit: for example, if an owner of a # route linked to a route in a discussion, the public has view access # to the route based on the discussion, but we wouldn't want someone # with view access to create a stealth secret, since the owner should # actively manage the stealth secret access via the gia sharing widget. # item.groups_access_load_from_db(qb) # g.assurt(item.groups_access) # else, stealth to permissions_free? # access_infer_id = item.get_access_infer(qb) if item.access_style_id == Access_Style.pub_editor: # Re prev. comments, ability to edit includes ability to make ssecs. if not item.can_edit(): raise GWIS_Error( 'You must be editor of public item to create link: %d' % (item_stack_id, )) elif item.access_style_id == Access_Style.usr_editor: g.assurt(item.can_edit()) elif item.access_style_id == Access_Style.restricted: # Restricted item types, like routes and track, can only have stealth # access created by their arbiters and owners. if not item.can_arbit(): raise GWIS_Error( 'You must be arbiter of restricted item to create link: %d' % (item_stack_id, )) # This is the path when an anonymous user gets a route and makes a # link for it. We'll call add_stealth_gia to make sure user has access # to the stealth secret. else: raise GWIS_Error('What you are trying to do is not supported.') if item.stealth_secret: new_stealth_secret = False stealth_secret = item.stealth_secret log.warning('fetch_n_save: Item already has link: %d' % (item_stack_id, )) else: new_stealth_secret = True stealth_secret = self.make_stealth_secret(item) # See also route_get: Maybe we should just INSERT INTO # group_item_access ourselves, but instead we sneakily # use the commit command to process a style_change. if ((qb.filters.gia_use_sessid) and (qb.username == conf.anonymous_username)): item_arr = [ item, ] self.req.db.transaction_retryable(self.add_stealth_gia, self.req, item_arr) item = item_arr[0] # The outermost document. ssecrets = etree.Element('ssecrets') # The response document. ssecret = etree.Element('ssecret') misc.xa_set(ssecret, 'stack_id', item.stack_id) misc.xa_set(ssecret, 'ssecret', stealth_secret) # Include the possibly changed access_infer_id. if new_stealth_secret: misc.xa_set(ssecret, 'acif', item.access_infer_id) # Include the possibly new and changed grac records. if item.groups_access is None: item.groups_access_load_from_db(qb) log.debug('fetch_n_save: no. groups_access: %d' % (len(item.groups_access), )) grac_doc = etree.Element('access_control') misc.xa_set(grac_doc, 'control_type', 'group_item_access') for grpa in item.groups_access.itervalues(): grpa.append_gml(grac_doc, need_digest=False) ssecret.append(grac_doc) # Bundle it up. ssecrets.append(ssecret) # And we're done. self.ssec_xml = ssecrets
def attrs_to_xml(self, elem, need_digest, extra_attrs=None, include_input_only_attrs=False): '''Add my attributes named in attrs as XML attributes of element elem.''' # The route finder uses extra_attrs, e.g., # [b'bonus_tagged', b'penalty_tagged', b'rating'] if need_digest: md5sum = hashlib.md5() # Only send attributes whose definition says it should be sent to client. if not include_input_only_attrs: attrs = self.gwis_defns[0] else: attrs = self.gwis_defns[1] if extra_attrs: for extra_attr in extra_attrs: attrs += ( extra_attr, extra_attr, ) #log.debug('attrs_to_xml: attrs: %s' % (str(attrs),)) # EXPLAIN: Not sure why we sort here.... #for attr in sorted(attrs): for attr in attrs: #log.debug(' >> attr: %s / %s' % (attr, getattr(self, attr),)) attr_val = getattr(self, attr[0], None) if attr_val is not None: misc.xa_set(elem, attr[1], attr_val) # FIXME: This code is probably obsolete, since Diff now does the checking when # the byways are checked out. # If we need a digest, we build an md5 hash based on the # non-geometric properties of the item. The digest is used when # diffing in the client to quickly tell if anything has changed. # - Certain property changes are meaningless, so we exclude them # FIXME I think this is broken... needs to be tested... # (okay i no longer think it's broken) # FIXME Move some of these to geofeature.py and make a class array # MAGIC_NUMBER: 0 is col_tuple[One.scol_pyname] if (need_digest and attr[0] not in ( #'system_id', #'branch_id', 'stack_id', 'version', 'geometry_len', 'beg_node_id', 'fin_node_id', # I missing a bunch of generated cols that probably should be excluded... 'node_lhs_elevation_m', 'node_rhs_elevation_m', 'split_from_stack_id', )): md5sum.update(attr[0] + str(getattr(self, attr[0]))) if (need_digest): # NOTE The GML elem 'dng' is called digest_nongeo in flashclient # FIXME Rename to rev_digest? misc.xa_set(elem, 'dng', md5sum.hexdigest())
def fetch_n_save(self): command.Op_Handler.fetch_n_save(self) # Assemble the qb from the request. qb = self.req.as_iqb(addons=False) g.assurt(qb.filters == Query_Filters(None)) item_stack_ids = qb.filters.decode_ids_compact('./ssec_sids', self.req.doc_in) if ((not item_stack_ids) or (item_stack_ids.find(',') != -1)): raise GWIS_Error('Expecting one stack ID in ssec_sids doc.') item_stack_id = int(item_stack_ids) if item_stack_id <= 0: raise GWIS_Error('Expecting positive valued stack ID.') items_fetched = item_user_access.Many() # So we get access_infer, etc. qb.filters.include_item_stack = True # Don't forget to maybe use the session ID. qb.filters.gia_use_sessid = self.req.filters.gia_use_sessid items_fetched.search_by_stack_id(item_stack_id, qb) if not items_fetched: raise GWIS_Error('Item stack ID not found or you cannot access: %d' % (item_stack_id,)) g.assurt(len(items_fetched) == 1) item = items_fetched[0] log.verbose1('verify_access: fetched: %s' % (str(item),)) g.assurt(not item.fresh) g.assurt(not item.valid) # What's the policy for who can make stealth secret values? # They're not really that special, but if every item in the database # had one, that's a lotta mozzarrella. # # Users should at least be able to edit: for example, if an owner of a # route linked to a route in a discussion, the public has view access # to the route based on the discussion, but we wouldn't want someone # with view access to create a stealth secret, since the owner should # actively manage the stealth secret access via the gia sharing widget. # item.groups_access_load_from_db(qb) # g.assurt(item.groups_access) # else, stealth to permissions_free? # access_infer_id = item.get_access_infer(qb) if item.access_style_id == Access_Style.pub_editor: # Re prev. comments, ability to edit includes ability to make ssecs. if not item.can_edit(): raise GWIS_Error( 'You must be editor of public item to create link: %d' % (item_stack_id,)) elif item.access_style_id == Access_Style.usr_editor: g.assurt(item.can_edit()) elif item.access_style_id == Access_Style.restricted: # Restricted item types, like routes and track, can only have stealth # access created by their arbiters and owners. if not item.can_arbit(): raise GWIS_Error( 'You must be arbiter of restricted item to create link: %d' % (item_stack_id,)) # This is the path when an anonymous user gets a route and makes a # link for it. We'll call add_stealth_gia to make sure user has access # to the stealth secret. else: raise GWIS_Error('What you are trying to do is not supported.') if item.stealth_secret: new_stealth_secret = False stealth_secret = item.stealth_secret log.warning('fetch_n_save: Item already has link: %d' % (item_stack_id,)) else: new_stealth_secret = True stealth_secret = self.make_stealth_secret(item) # See also route_get: Maybe we should just INSERT INTO # group_item_access ourselves, but instead we sneakily # use the commit command to process a style_change. if ((qb.filters.gia_use_sessid) and (qb.username == conf.anonymous_username)): item_arr = [item,] self.req.db.transaction_retryable(self.add_stealth_gia, self.req, item_arr) item = item_arr[0] # The outermost document. ssecrets = etree.Element('ssecrets') # The response document. ssecret = etree.Element('ssecret') misc.xa_set(ssecret, 'stack_id', item.stack_id) misc.xa_set(ssecret, 'ssecret', stealth_secret) # Include the possibly changed access_infer_id. if new_stealth_secret: misc.xa_set(ssecret, 'acif', item.access_infer_id) # Include the possibly new and changed grac records. if item.groups_access is None: item.groups_access_load_from_db(qb) log.debug('fetch_n_save: no. groups_access: %d' % (len(item.groups_access),)) grac_doc = etree.Element('access_control') misc.xa_set(grac_doc, 'control_type', 'group_item_access') for grpa in item.groups_access.itervalues(): grpa.append_gml(grac_doc, need_digest=False) ssecret.append(grac_doc) # Bundle it up. ssecrets.append(ssecret) # And we're done. self.ssec_xml = ssecrets
def as_xml(self): # SYNC_ME: gwis.command_.geocode.fetch_n_save # and item.util.address.as_xml elem = etree.Element('addr') misc.xa_set(elem, 'text', self.text) misc.xa_set(elem, 'x', self.x) misc.xa_set(elem, 'y', self.y) misc.xa_set(elem, 'width', self.width) misc.xa_set(elem, 'height', self.height) #misc.xa_set(elem, 'street', self.street) #misc.xa_set(elem, 'city', self.city) #misc.xa_set(elem, 'county', self.county) #misc.xa_set(elem, 'state', self.state) #misc.xa_set(elem, 'zip', self.zip) #misc.xa_set(elem, 'country', self.country) # BUG nnnn: Should flashclient use the entity type? misc.xa_set(elem, 'gc_fulfiller', self.gc_fulfiller) misc.xa_set(elem, 'gc_confidence', self.gc_confidence) return elem
def fetch_n_save(self): command.Op_Handler.fetch_n_save(self) # Assemble the qb from the request. qb = self.req.as_iqb(addons=False) g.assurt(qb.filters == Query_Filters(None)) item_stack_ids = qb.filters.decode_ids_compact('./fbil_sids', self.req.doc_in) if ((not item_stack_ids) or (item_stack_ids.find(',') != -1)): raise GWIS_Error('Expecting one stack ID in fbil_sids doc.') items = item_user_access.Many() g.assurt(qb.sql_clauses is None) qb.sql_clauses = item_user_access.Many.sql_clauses_cols_all.clone() qb.filters.dont_load_feat_attcs = True qb.filters.only_stack_ids = item_stack_ids sql_many = items.search_get_sql(qb) sql_itmf = ( """ SELECT itmf.item_stack_id , itmf.username -- , itmf.user_id , itmf.library_squelch , itmf.show_in_history -- , itmf.last_viewed -- , itmf.branch_id FROM (%s) AS gia JOIN item_findability AS itmf ON (gia.stack_id = itmf.item_stack_id) WHERE ( (itmf.username = %s) AND (gia.access_level_id <= %d)) OR ( (itmf.username = %s) AND (gia.access_level_id <= %d)) """ % (sql_many, qb.db.quoted(conf.anonymous_username), Access_Level.arbiter, qb.db.quoted(qb.username), Access_Level.viewer,)) rows = qb.db.sql(sql_itmf) if len(rows) == 0: raise GWIS_Warning('No item_findability for user-item.') # We'll find one or two records: one for the user, and one for the # public. # EXPLAIN: What records exist for anon route request? # [lb] thinks it's a GIA record with a stealth secret, # but probably not an item_findability record. # # FIXME: TEST: anon get route, log in, save route # fbilities = etree.Element('fbilities') for row in rows: fbility = etree.Element('fbility') misc.xa_set(fbility, 'sid', row['item_stack_id']) if row['username'] == conf.anonymous_username: # MAGIC_NUMBER: Usernames prefixed with a floorbar are special. username_hacked = '_anonymous' else: username_hacked = row['username'] misc.xa_set(fbility, 'unom', username_hacked) #misc.xa_set(fbility, 'uid', row['user_id']) misc.xa_set(fbility, 'sqel', row['library_squelch']) misc.xa_set(fbility, 'hist', row['show_in_history']) #misc.xa_set(fbility, 'lstv', row['last_viewed']) #misc.xa_set(fbility, 'brid', row['branch_id']) fbilities.append(fbility) self.itmf_xml = fbilities