def test_search(self): persons = [TestPerson(first_name='Bryan', last_name='abc'), TestPerson(first_name='Bryan', last_name='abcef'), TestPerson(first_name='abc', last_name='Bryan'), TestPerson(first_name='Bryan abc', last_name='efg'), TestPerson(first_name='AAAA BBBB', last_name='CCC DDD')] for p in persons: indexing.update_index_properties(p) db.put(p) res = indexing.search(TestPerson.all(), TextQuery('Bryan abc'), 1) assert [(p.first_name, p.last_name) for p in res] == [('Bryan', 'abc')] res = indexing.search(TestPerson.all(), TextQuery('CC AAAA'), 100) assert [(p.first_name, p.last_name) for p in res] == \ [('AAAA BBBB', 'CCC DDD')]
def get(self): if self.config.search_auth_key_required and not (self.auth and self.auth.search_permission): return self.error(403, "Missing or invalid authorization key\n") pfif_version = pfif.PFIF_VERSIONS.get(self.params.version or "1.2") # Retrieve parameters and do some sanity checks on them. query_string = self.request.get("q") subdomain = self.request.get("subdomain") max_results = min(self.params.max_results or 100, HARD_MAX_RESULTS) if not query_string: return self.error(400, "Missing q parameter") if not subdomain: return self.error(400, "Missing subdomain parameter") # Perform the search. results = indexing.search(Person.all_in_subdomain(subdomain), TextQuery(query_string), max_results) records = [pfif_version.person_to_dict(result) for result in results] utils.optionally_filter_sensitive_fields(records, self.auth) # Define the function to retrieve notes for a person. def get_notes_for_person(person): notes = model.Note.get_by_person_record_id(self.subdomain, person["person_record_id"]) records = map(pfif_version.note_to_dict, notes) utils.optionally_filter_sensitive_fields(records, self.auth) return records self.response.headers["Content-Type"] = "application/xml" pfif_version.write_file(self.response.out, records, get_notes_for_person)
def test_search(self): persons = [create_person(given_name='Bryan', family_name='abc'), create_person(given_name='Bryan', family_name='abcef'), create_person(given_name='abc', family_name='Bryan'), create_person(given_name='Bryan abc', family_name='efg'), create_person(given_name='AAAA BBBB', family_name='CCC DDD')] for p in persons: indexing.update_index_properties(p) db.put(p) res = indexing.search('test', TextQuery('Bryan abc'), 1) assert [(p.given_name, p.family_name) for p in res] == [('Bryan', 'abc')] res = indexing.search('test', TextQuery('CC AAAA'), 100) assert [(p.given_name, p.family_name) for p in res] == \ [('AAAA BBBB', 'CCC DDD')]
def get(self): if self.config.search_auth_key_required and not ( self.auth and self.auth.search_permission): return self.error(403, 'Missing or invalid authorization key\n') pfif_version = pfif.PFIF_VERSIONS.get(self.params.version or '1.2') # Retrieve parameters and do some sanity checks on them. query_string = self.request.get("q") subdomain = self.request.get("subdomain") max_results = min(self.params.max_results or 100, HARD_MAX_RESULTS) if not query_string: return self.error(400, 'Missing q parameter') if not subdomain: return self.error(400, 'Missing subdomain parameter') # Perform the search. results = indexing.search(Person.all_in_subdomain(subdomain), TextQuery(query_string), max_results) records = [pfif_version.person_to_dict(result) for result in results] utils.optionally_filter_sensitive_fields(records, self.auth) # Define the function to retrieve notes for a person. def get_notes_for_person(person): notes = model.Note.get_by_person_record_id( self.subdomain, person['person_record_id']) records = map(pfif_version.note_to_dict, notes) utils.optionally_filter_sensitive_fields(records, self.auth) return records self.response.headers['Content-Type'] = 'application/xml' pfif_version.write_file(self.response.out, records, get_notes_for_person)
def search(self, query_name, query_location=None): """Get results for a query. Args: query_name: A name to query for (string). query_location: A location to query for (optional, string). """ text_query = TextQuery( '%s %s' % (query_name, query_location) if query_location else query_name) results = None if self._external_search_backends: results = external_search.search(self._repo, text_query, self._max_results, self._external_search_backends) # External search backends are not always complete. Fall back to the # original search when they fail or return no results. if not results: query_dict = {'name': query_name} if query_location: query_dict['location'] = query_location if self._enable_fulltext_search: results = full_text_search.search(self._repo, query_dict, self._max_results) else: results = indexing.search(self._repo, text_query, self._max_results) return results
def get(self): if self.config.search_auth_key_required and not ( self.auth and self.auth.search_permission): self.info(403, message='Missing or invalid authorization key', style='plain') return pfif_version = self.params.version # Retrieve parameters and do some sanity checks on them. record_id = self.request.get('id') query_string = self.request.get('q') max_results = min(self.params.max_results or 100, HARD_MAX_RESULTS) results = [] if record_id: # Search by record ID (always returns just 1 result or nothing). person = model.Person.get(self.repo, record_id) if person: results = [person] elif query_string: # Search by query words. if self.config.external_search_backends: query = TextQuery(query_string) results = external_search.search( self.repo, query, max_results, self.config.external_search_backends) # External search backends are not always complete. Fall back to # the original search when they fail or return no results. if not results: if config.get('enable_fulltext_search'): results = full_text_search.search(self.repo, query_string, max_results) else: results = indexing.search(self.repo, TextQuery(query_string), max_results) else: self.info(400, message='Neither id nor q parameter specified', style='plain') records = [pfif_version.person_to_dict(result) for result in results] utils.optionally_filter_sensitive_fields(records, self.auth) # Define the function to retrieve notes for a person. def get_notes_for_person(person): notes = model.Note.get_by_person_record_id( self.repo, person['person_record_id']) notes = [note for note in notes if not note.hidden] records = map(pfif_version.note_to_dict, notes) utils.optionally_filter_sensitive_fields(records, self.auth) return records self.response.headers[ 'Content-Type'] = 'application/xml; charset=utf-8' pfif_version.write_file(self.response.out, records, get_notes_for_person) utils.log_api_action(self, ApiActionLog.SEARCH, len(records))
def search(self, query_txt): """Performs a search and adds view_url attributes to the results.""" results = None if self.config.external_search_backends: results = external_search.search( self.repo, TextQuery(query_txt), MAX_RESULTS, self.config.external_search_backends) # External search backends are not always complete. Fall back to the # original search when they fail or return no results. if not results: if config.get('enable_fulltext_search'): results = full_text_search.search(self.repo, query_txt, MAX_RESULTS) else: results = indexing.search(self.repo, TextQuery(query_txt), MAX_RESULTS) for result in results: result.view_url = self.get_url('/view', id=result.record_id, role=self.params.role, query=self.params.query, given_name=self.params.given_name, family_name=self.params.family_name) result.latest_note_status = get_person_status_text(result) if result.is_clone(): result.provider_name = result.get_original_domain() result.should_show_inline_photo = (self.should_show_inline_photo( result.photo_url)) sanitize_urls(result) return results
def search(self, query_txt, role=None): """Performs a search and adds view_url attributes to the results.""" results = None if self.config.external_search_backends: results = external_search.search( self.repo, TextQuery(query_txt), MAX_RESULTS, self.config.external_search_backends) # External search backends are not always complete. Fall back to the # original search when they fail or return no results. if not results: if config.get('enable_fulltext_search'): results = full_text_search.search(self.repo, query_txt, MAX_RESULTS) else: results = indexing.search(self.repo, TextQuery(query_txt), MAX_RESULTS, role=self.params.role) for result in results: result.view_url = self.get_url('/view', id=result.record_id, role=self.params.role, query=self.params.query, given_name=self.params.given_name, family_name=self.params.family_name) result.latest_note_status = get_person_status_text(result) if result.is_clone(): result.provider_name = result.get_original_domain() sanitize_urls(result) return results
def post(self): if not (self.auth and self.auth.search_permission): self.info( 403, message='"key" URL parameter is either missing, invalid or ' 'lacks required permissions. The key\'s repo must be "*" ' "and search_permission must be True.", style="plain", ) return body = self.request.body_file.read() doc = xml.dom.minidom.parseString(body) message_text = self.get_element_text(doc, "message_text") receiver_phone_number = self.get_element_text(doc, "receiver_phone_number") if message_text is None: self.info(400, message="message_text element is required.", style="plain") return if receiver_phone_number is None: self.info(400, message="receiver_phone_number element is required.", style="plain") return repo = self.config.sms_number_to_repo and self.config.sms_number_to_repo.get(receiver_phone_number) if not repo: self.info( 400, message="The given receiver_phone_number is not found in " "sms_number_to_repo config.", style="plain", ) return responses = [] m = re.search(r"^search\s+(.+)$", message_text.strip(), re.I) if m: query_string = m.group(1).strip() query = TextQuery(query_string) persons = indexing.search(repo, query, HandleSMS.MAX_RESULTS) if persons: for person in persons: responses.append(self.render_person(person)) else: responses.append("No results found for: %s" % query_string) responses.append("More at: google.org/personfinder/%s?ui=light" % repo) responses.append( "All data entered in Person Finder is available to the public " "and usable by anyone. Google does not review or verify the " "accuracy of this data google.org/personfinder/global/tos.html" ) else: responses.append("Usage: Search John") self.response.headers["Content-Type"] = "application/xml" self.write( '<?xml version="1.0" encoding="utf-8"?>\n' "<response>\n" " <message_text>%s</message_text>\n" "</response>\n" % django.utils.html.escape(" ## ".join(responses)) )
def get(self): if self.config.search_auth_key_required and not ( self.auth and self.auth.search_permission): self.info( 403, message='Missing or invalid authorization key', style='plain') return pfif_version = self.params.version # Retrieve parameters and do some sanity checks on them. record_id = self.request.get('id') query_string = self.request.get('q') max_results = min(self.params.max_results or 100, HARD_MAX_RESULTS) results = [] if record_id: # Search by record ID (always returns just 1 result or nothing). person = model.Person.get(self.repo, record_id) if person: results = [person] elif query_string: # Search by query words. if self.config.external_search_backends: query = TextQuery(query_string) results = external_search.search(self.repo, query, max_results, self.config.external_search_backends) # External search backends are not always complete. Fall back to # the original search when they fail or return no results. if not results: if config.get('enable_fulltext_search'): results = full_text_search.search( self.repo, query_string, max_results) else: results = indexing.search( self.repo, TextQuery(query_string), max_results) else: self.info( 400, message='Neither id nor q parameter specified', style='plain') records = [pfif_version.person_to_dict(result) for result in results] utils.optionally_filter_sensitive_fields(records, self.auth) # Define the function to retrieve notes for a person. def get_notes_for_person(person): notes = model.Note.get_by_person_record_id( self.repo, person['person_record_id']) notes = [note for note in notes if not note.hidden] records = map(pfif_version.note_to_dict, notes) utils.optionally_filter_sensitive_fields(records, self.auth) return records self.response.headers['Content-Type'] = 'application/xml; charset=utf-8' pfif_version.write_file( self.response.out, records, get_notes_for_person) utils.log_api_action(self, ApiActionLog.SEARCH, len(records))
def test_search(self): persons = [ TestPerson(first_name='Bryan', last_name='abc'), TestPerson(first_name='Bryan', last_name='abcef'), TestPerson(first_name='abc', last_name='Bryan'), TestPerson(first_name='Bryan abc', last_name='efg'), TestPerson(first_name='AAAA BBBB', last_name='CCC DDD') ] for p in persons: indexing.update_index_properties(p) db.put(p) res = indexing.search(TestPerson.all(), TextQuery('Bryan abc'), 1) assert [(p.first_name, p.last_name) for p in res] == [('Bryan', 'abc')] res = indexing.search(TestPerson.all(), TextQuery('CC AAAA'), 100) assert [(p.first_name, p.last_name) for p in res] == \ [('AAAA BBBB', 'CCC DDD')]
def test_search(self): persons = [ create_person(given_name='Bryan', family_name='abc'), create_person(given_name='Bryan', family_name='abcef'), create_person(given_name='abc', family_name='Bryan'), create_person(given_name='Bryan abc', family_name='efg'), create_person(given_name='AAAA BBBB', family_name='CCC DDD') ] for p in persons: indexing.update_index_properties(p) db.put(p) res = indexing.search('test', TextQuery('Bryan abc'), 1) assert [(p.given_name, p.family_name) for p in res] == [('Bryan', 'abc')] res = indexing.search('test', TextQuery('CC AAAA'), 100) assert [(p.given_name, p.family_name) for p in res] == \ [('AAAA BBBB', 'CCC DDD')]
def search(self, query_dict): """ Performs a search and adds view_url attributes to the results. Args: query_dict: A list contains two queries: Name query and Location query """ query_txt = " ".join(query_dict.values()) results = None if self.config.external_search_backends: results = external_search.search( self.repo, TextQuery(query_txt), MAX_RESULTS, self.config.external_search_backends) # External search backends are not always complete. Fall back to the # original search when they fail or return no results. if not results: if config.get('enable_fulltext_search'): results = full_text_search.search(self.repo, query_dict, MAX_RESULTS) else: results = indexing.search(self.repo, TextQuery(query_txt), MAX_RESULTS) query_name = self.get_query_value() for result in results: result.view_url = self.get_url('/view', id=result.record_id, role=self.params.role, query_name=query_name, query_location= self.params.query_location, given_name=self.params.given_name, family_name=self.params.family_name) result.latest_note_status = get_person_status_text(result) if result.is_clone(): result.provider_name = result.get_original_domain() result.should_show_inline_photo = ( self.should_show_inline_photo(result.photo_url)) if result.should_show_inline_photo and result.photo: # Only use a thumbnail URL if the photo was uploaded; we don't # have thumbnails for other photos. result.thumbnail_url = self.get_thumbnail_url(result.photo_url) sanitize_urls(result) return results
def search(self, query_name, query_location=None): """Get results for a query. Args: query_name: A name to query for (string). query_location: A location to query for (optional, string). """ if self._enable_fulltext_search: query_dict = {'name': query_name} if query_location: query_dict['location'] = query_location return full_text_search.search(self._repo, query_dict, self._max_results) else: text_query = TextQuery( '%s %s' % (query_name, query_location) if query_location else query_name) return indexing.search(self._repo, text_query, self._max_results)
def search(self, query_name, query_location=None): """Get results for a query. Args: query_name: A name to query for (string). query_location: A location to query for (optional, string). """ if self._enable_fulltext_search: query_dict = {'name': query_name} if query_location: query_dict['location'] = query_location return full_text_search.search( self._repo, query_dict, self._max_results) else: text_query = TextQuery( '%s %s' % (query_name, query_location) if query_location else query_name) return indexing.search( self._repo, text_query, self._max_results)
def post(self): if not (self.auth and self.auth.search_permission and self.auth.domain_write_permission == '*'): self.info( 403, message= '"key" URL parameter is either missing, invalid or ' 'lacks required permissions. The key\'s repo must be "*", ' 'search_permission must be True, and it must have write ' 'permission with domain name "*".', style='plain') return body = self.request.body_file.read() doc = xml.dom.minidom.parseString(body) message_text = self.get_element_text(doc, 'message_text') receiver_phone_number = self.get_element_text( doc, 'receiver_phone_number') if message_text is None: self.info( 400, message='message_text element is required.', style='plain') return if receiver_phone_number is None: self.info( 400, message='receiver_phone_number element is required.', style='plain') return repo = ( self.config.sms_number_to_repo and self.config.sms_number_to_repo.get(receiver_phone_number)) if not repo: self.info( 400, message= 'The given receiver_phone_number is not found in ' 'sms_number_to_repo config.', style='plain') return query_lang = None query_action = None match = None for lang, action, regex in HandleSMS.QUERY_PATTERNS: match = re.search(regex, message_text.strip(), re.I) if match: query_lang = lang query_action = action break if query_lang: # Use the language for the following calls of _(). django_setup.activate(query_lang) responses = [] if query_action == 'search': query_string = match.group(1).strip() query = TextQuery(query_string) persons = indexing.search(repo, query, HandleSMS.MAX_RESULTS) if persons: for person in persons: responses.append(self.render_person(person)) else: responses.append( _('No results found for: %(query)s') % {'query': query_string}) responses.append( _('More at: %(url)s') % {'url': 'google.org/personfinder/%s?ui=light' % repo}) responses.append( _('All data entered in Person Finder is available to the ' 'public and usable by anyone. Google does not review or ' 'verify the accuracy of this data ' 'google.org/personfinder/global/tos')) elif self.config.enable_sms_record_input and query_action == 'add': name_string = match.group(1).strip() person = Person.create_original( repo, entry_date=utils.get_utcnow(), full_name=name_string, family_name='', given_name='') person.update_index(['old', 'new']) note = Note.create_original( repo, entry_date=utils.get_utcnow(), source_date=utils.get_utcnow(), person_record_id=person.record_id, author_name=name_string, author_made_contact=True, status='is_note_author', text=message_text) db.put(note) model.UserActionLog.put_new('add', note, copy_properties=False) person.update_from_note(note) db.put(person) model.UserActionLog.put_new('add', person, copy_properties=False) responses.append(_('Added a record for: %(person_name)s') % {'person_name': name_string}) else: usage_str = 'Usage: "Search John"' if self.config.enable_sms_record_input: usage_str += ' OR "I am John"' responses.append(usage_str) # Convert the response into ASCII because the SMS pipeline doesn't # support UTF-8. # e.g., It removes diacritics such as "ú" -> "u". # This seems acceptable for Spanish, but may not be for other # languages. ascii_response = unidecode(u' ## '.join(responses)) self.response.headers['Content-Type'] = 'application/xml; charset=utf-8' self.write( '<?xml version="1.0" encoding="utf-8"?>\n' '<response>\n' ' <message_text>%s</message_text>\n' '</response>\n' % django.utils.html.escape(ascii_response))
def post(self): if not (self.auth and self.auth.search_permission): self.info( 403, message='"key" URL parameter is either missing, invalid or ' 'lacks required permissions. The key\'s repo must be "*" ' 'and search_permission must be True.', style='plain') return body = self.request.body_file.read() doc = xml.dom.minidom.parseString(body) message_text = self.get_element_text(doc, 'message_text') receiver_phone_number = self.get_element_text(doc, 'receiver_phone_number') if message_text is None: self.info(400, message='message_text element is required.', style='plain') return if receiver_phone_number is None: self.info(400, message='receiver_phone_number element is required.', style='plain') return repo = (self.config.sms_number_to_repo and self.config.sms_number_to_repo.get(receiver_phone_number)) if not repo: self.info( 400, message='The given receiver_phone_number is not found in ' 'sms_number_to_repo config.', style='plain') return responses = [] m = re.search(r'^search\s+(.+)$', message_text.strip(), re.I) if m: query_string = m.group(1).strip() query = TextQuery(query_string) persons = indexing.search(repo, query, HandleSMS.MAX_RESULTS) if persons: for person in persons: responses.append(self.render_person(person)) else: responses.append('No results found for: %s' % query_string) responses.append('More at: google.org/personfinder/%s?ui=light' % repo) responses.append( 'All data entered in Person Finder is available to the public ' 'and usable by anyone. Google does not review or verify the ' 'accuracy of this data google.org/personfinder/global/tos.html' ) else: responses.append('Usage: Search John') self.response.headers['Content-Type'] = 'application/xml' self.write('<?xml version="1.0" encoding="utf-8"?>\n' '<response>\n' ' <message_text>%s</message_text>\n' '</response>\n' % django.utils.html.escape(' ## '.join(responses)))
def test_no_query_terms(self): # Regression test (this used to throw an exception). assert indexing.search('test', TextQuery(''), 100) == []
def get_matches(self, query, limit=100): results = indexing.search('test', TextQuery(query), limit) return [(p.given_name, p.family_name) for p in results]
def post(self): if not (self.auth and self.auth.search_permission and self.auth.domain_write_permission == '*'): self.info( 403, message= '"key" URL parameter is either missing, invalid or ' 'lacks required permissions. The key\'s repo must be "*", ' 'search_permission must be True, and it must have write ' 'permission.', style='plain') return body = self.request.body_file.read() doc = xml.dom.minidom.parseString(body) message_text = self.get_element_text(doc, 'message_text') receiver_phone_number = self.get_element_text( doc, 'receiver_phone_number') if message_text is None: self.info( 400, message='message_text element is required.', style='plain') return if receiver_phone_number is None: self.info( 400, message='receiver_phone_number element is required.', style='plain') return repo = ( self.config.sms_number_to_repo and self.config.sms_number_to_repo.get(receiver_phone_number)) if not repo: self.info( 400, message= 'The given receiver_phone_number is not found in ' 'sms_number_to_repo config.', style='plain') return responses = [] search_m = re.search(r'^search\s+(.+)$', message_text.strip(), re.I) add_self_m = re.search(r'^i am\s+(.+)$', message_text.strip(), re.I) if search_m: query_string = search_m.group(1).strip() query = TextQuery(query_string) persons = indexing.search(repo, query, HandleSMS.MAX_RESULTS) if persons: for person in persons: responses.append(self.render_person(person)) else: responses.append('No results found for: %s' % query_string) responses.append( 'More at: google.org/personfinder/%s?ui=light' % repo) responses.append( 'All data entered in Person Finder is available to the public ' 'and usable by anyone. Google does not review or verify the ' 'accuracy of this data google.org/personfinder/global/tos.html') elif self.config.enable_sms_record_input and add_self_m: name_string = add_self_m.group(1).strip() person = Person.create_original( repo, entry_date=utils.get_utcnow(), full_name=name_string, family_name='', given_name='') person.update_index(['old', 'new']) note = Note.create_original( repo, entry_date=utils.get_utcnow(), source_date=utils.get_utcnow(), person_record_id=person.record_id, author_name=name_string, author_made_contact=True, status='is_note_author', text=message_text) db.put(note) model.UserActionLog.put_new('add', note, copy_properties=False) person.update_from_note(note) db.put(person) model.UserActionLog.put_new('add', person, copy_properties=False) responses.append('Added record for found person: %s' % name_string) else: usage_str = 'Usage: "Search John"' if self.config.enable_sms_record_input: usage_str += ' OR "I am John"' responses.append(usage_str) self.response.headers['Content-Type'] = 'application/xml' self.write( '<?xml version="1.0" encoding="utf-8"?>\n' '<response>\n' ' <message_text>%s</message_text>\n' '</response>\n' % django.utils.html.escape(' ## '.join(responses)))
def get_matches(self, query, limit=100): results = indexing.search(TestPerson.all(), TextQuery(query), limit) return [(p.first_name, p.last_name) for p in results]
def post(self): if not (self.auth and self.auth.search_permission and self.auth.domain_write_permission == '*'): self.info( 403, message='"key" URL parameter is either missing, invalid or ' 'lacks required permissions. The key\'s repo must be "*", ' 'search_permission must be True, and it must have write ' 'permission.', style='plain') return body = self.request.body_file.read() doc = xml.dom.minidom.parseString(body) message_text = self.get_element_text(doc, 'message_text') receiver_phone_number = self.get_element_text(doc, 'receiver_phone_number') if message_text is None: self.info(400, message='message_text element is required.', style='plain') return if receiver_phone_number is None: self.info(400, message='receiver_phone_number element is required.', style='plain') return repo = (self.config.sms_number_to_repo and self.config.sms_number_to_repo.get(receiver_phone_number)) if not repo: self.info( 400, message='The given receiver_phone_number is not found in ' 'sms_number_to_repo config.', style='plain') return responses = [] search_m = re.search(r'^search\s+(.+)$', message_text.strip(), re.I) add_self_m = re.search(r'^i am\s+(.+)$', message_text.strip(), re.I) if search_m: query_string = search_m.group(1).strip() query = TextQuery(query_string) persons = indexing.search(repo, query, HandleSMS.MAX_RESULTS) if persons: for person in persons: responses.append(self.render_person(person)) else: responses.append('No results found for: %s' % query_string) responses.append('More at: google.org/personfinder/%s?ui=light' % repo) responses.append( 'All data entered in Person Finder is available to the public ' 'and usable by anyone. Google does not review or verify the ' 'accuracy of this data google.org/personfinder/global/tos.html' ) elif self.config.enable_sms_record_input and add_self_m: name_string = add_self_m.group(1).strip() person = Person.create_original(repo, entry_date=utils.get_utcnow(), full_name=name_string, family_name='', given_name='') person.update_index(['old', 'new']) note = Note.create_original(repo, entry_date=utils.get_utcnow(), source_date=utils.get_utcnow(), person_record_id=person.record_id, author_name=name_string, author_made_contact=True, status='is_note_author', text=message_text) db.put(note) model.UserActionLog.put_new('add', note, copy_properties=False) person.update_from_note(note) db.put(person) model.UserActionLog.put_new('add', person, copy_properties=False) responses.append('Added record for found person: %s' % name_string) else: usage_str = 'Usage: "Search John"' if self.config.enable_sms_record_input: usage_str += ' OR "I am John"' responses.append(usage_str) self.response.headers['Content-Type'] = 'application/xml' self.write('<?xml version="1.0" encoding="utf-8"?>\n' '<response>\n' ' <message_text>%s</message_text>\n' '</response>\n' % django.utils.html.escape(' ## '.join(responses)))