# HG changeset patch # User Todd Larsen # Date 1222997554 0 # Node ID 9b39d93b677fc6f6b91de1c8ed9bc7dc2a9ac5a3 # Parent 52a42831d9d6301b9503ea84926c21b906a49caa Make findNearestUsers() code in soc/logic/site/id_user.py more generic and move it to soc/logic/model.py. Orginal findNearest...() functions in id_user.py are now convenience wrappers. Add typed-query string construction functions to model.py. Move getFulLClassName() from key_name.py model.py, since it has more to do with Model types than key names. Swap 'offset' and 'limit' and make 'limit' arguments non-optional. Also, stop adding 1 inside the ...ForLimitAndOffset() functions and make the callers do it (since it was being added for a very UI-specific reason of whether or not to display a "Next>" link). Patch by: Todd Larsen Review by: Pawel Solyga Review URL: http://codereviews.googleopensourceprograms.com/1401 diff -r 52a42831d9d6 -r 9b39d93b677f app/soc/logic/document.py --- a/app/soc/logic/document.py Thu Oct 02 20:22:15 2008 +0000 +++ b/app/soc/logic/document.py Fri Oct 03 01:32:34 2008 +0000 @@ -31,10 +31,9 @@ from soc.logic import out_of_band from soc.logic.site import id_user -import soc.logic.model +from soc.logic import model import soc.models.document -import soc.models.work def getDocument(path, link_name=None): @@ -114,20 +113,3 @@ # got an existing one due to a race, so update with document_properties anyway, # in a transaction return soc.logic.model.updateModelProperties(document, **document_properties) - - -def getWorksForOffsetAndLimit(offset=0, limit=0, cls=soc.models.work.Work): - """Returns Works for given offset and limit or None if not found. - - Args: - offset: offset in entities list which defines first entity to return - limit: max amount of entities to return - cls: Model class of items to return (including sub-classes of that type); - default is Work - """ - query = db.GqlQuery( - 'SELECT * FROM Work WHERE inheritance_line = :1 ORDER BY title', - key_name.getFullClassName(cls)) - - # Fetch one more to see if there should be a 'next' link - return query.fetch(limit+1, offset) diff -r 52a42831d9d6 -r 9b39d93b677f app/soc/logic/key_name.py --- a/app/soc/logic/key_name.py Thu Oct 02 20:22:15 2008 +0000 +++ b/app/soc/logic/key_name.py Fri Oct 03 01:32:34 2008 +0000 @@ -31,11 +31,6 @@ pass -def getFullClassName(cls): - """Returns fully-qualified module.class name string.""" - return '%s.%s' % (cls.__module__, cls.__name__) - - def nameDocument(partial_path, link_name=None): """Returns a Document key name constructed from a path and link name. diff -r 52a42831d9d6 -r 9b39d93b677f app/soc/logic/model.py --- a/app/soc/logic/model.py Thu Oct 02 20:22:15 2008 +0000 +++ b/app/soc/logic/model.py Fri Oct 03 01:32:34 2008 +0000 @@ -19,11 +19,163 @@ __authors__ = [ '"Pawel Solyga" ', + '"Todd Larsen" ', ] + from google.appengine.ext import db +def getFullClassName(cls): + """Returns fully-qualified module.class name string.""" + return '%s.%s' % (cls.__module__, cls.__name__) + + +def buildTypedQueryString(base_class, derived_class=None): + """Returns a GQL query string compatible with PolyModel. + + Args: + base_class: Model class that inherits directly from + polymodel.PolyModel, such as soc.models.work.Work + derived_class: optional more-specific Model class that + derives from base_class, such as soc.model.document.Document; + default is None, in which case the inheritance_line + property is *not* tested by the returned query string + """ + query_str_parts = ['SELECT * FROM ', base_class.__name__] + + if derived_class: + query_str_parts.extend( + [" WHERE inheritance_line = '", getFullClassName(derived_class), "'"]) + + return ''.join(query_str_parts) + + +def buildOrderedQueryString(base_class, derived_class=None, order_by=None): + """Returns a an ordered GQL query string compatible with PolyModel. + + Args: + base_class, derived_class: see buildTypedQueryString() + order_by: optional field name by which to order the query results; + default is None, in which case no ORDER BY clause is placed in + the query string + """ + query_str_parts = [ + buildTypedQueryString(base_class, derived_class=derived_class)] + + if order_by: + query_str_parts.extend([' ORDER BY ', order_by]) + + return ''.join(query_str_parts) + + +def getEntitiesForLimitAndOffset(base_class, limit, offset=0, + derived_class=None, order_by=None): + """Returns entities for given offset and limit or None if not found. + + Args: + limit: max amount of entities to return + offset: optional offset in entities list which defines first entity to + return; default is zero (first entity) + """ + query_string = buildOrderedQueryString( + base_class, derived_class=derived_class, order_by=order_by) + + query = db.GqlQuery(query_string) + + return query.fetch(limit, offset) + + +def getNearestEntities(base_class, fields_to_try, derived_class=None): + """Get entities just before and just after the described entity. + + Args: + fields_to_try: ordered list of key/value pairs that "describe" + the desired entity (which may not necessarily exist), where key is + the name of the field, and value is an instance of that field + used in the comparison; if value is None, that field is skipped + + Returns: + a two-tuple: ([nearest_entities], 'field_name') + + nearest_entities: list of entities being those just before and just + after the (possibly non-existent) entity identified by the first + of the supplied (non-None) fields + OR + possibly None if query had no results for the supplied field + that was used. + """ + # SELECT * FROM base_class WHERE inheritance_line = 'derived_class' + typed_query_str = buildTypedQueryString( + base_class, derived_class=derived_class) + + if derived_class: + typed_query_str = typed_query_str + ' AND ' + else: + typed_query_str = typed_query_str + ' WHERE ' + + for field, value in fields_to_try: + if value is None: + # skip this not-supplied field + continue + + query = db.GqlQuery('%s%s > :1' % (typed_query_str, field), value) + return query.fetch(1), field + + # all fields exhausted, and all had None values + return (None, None) + + +def findNearestEntitiesOffset(width, base_class, fields_to_try, + derived_class=None): + """Finds offset of beginning of a range of entities around the nearest. + + Args: + width: the width of the "found" window around the nearest User found + base_class, fields_to_try, derived_class: see getNearestEntities() + + Returns: + an offset into the list of entities that is width/2 less than the + offset of the first entity returned by getNearestEntities(), or zero + if that offset would be less than zero + OR + None if there are no nearest entities or the offset of the beginning of + the range cannot be found for some reason + """ + # find entity "nearest" to supplied fields + nearest_entities, field = getNearestEntities( + base_class, fields_to_try, derived_class=derived_class) + + if not nearest_entities: + # no "nearest" entity, so indicate that with None + return None + + nearest_entity = nearest_entities[0] + + # start search for beginning of nearest Users range at offset zero + offset = 0 + entities = getEntitiesForLimitAndOffset( + base_class, width, offset=offset, derived_class=derived_class) + + while True: + for entity in entities: + if getattr(nearest_entity, field) == getattr(entity, field): + # nearest User found in current search range, so return a range start + return max(0, (offset - (width/2))) + + offset = offset + 1 + + # nearest User was not in the current search range, so fetch the next set + entities = getEntitiesForLimitAndOffset( + base_class, width, offset=offset, derived_class=derived_class) + + if not entities: + # nearest User never found, so indicate that with None + break + + return None + + def updateModelProperties(model, **model_properties): """Update existing model entity using supplied model properties. diff -r 52a42831d9d6 -r 9b39d93b677f app/soc/logic/site/id_user.py --- a/app/soc/logic/site/id_user.py Thu Oct 02 20:22:15 2008 +0000 +++ b/app/soc/logic/site/id_user.py Fri Oct 03 01:32:34 2008 +0000 @@ -29,6 +29,7 @@ from google.appengine.ext import db from soc.logic import key_name +from soc.logic import model from soc.logic import out_of_band import soc.models.user @@ -69,17 +70,16 @@ return id -def getUsersForOffsetAndLimit(offset=0, limit=0): +def getUsersForLimitAndOffset(limit, offset=0): """Returns Users entities for given offset and limit or None if not found. Args: - offset: offset in entities list which defines first entity to return limit: max amount of entities to return + offset: optional offset in entities list which defines first entity to + return; default is zero (first entity) """ - query = db.GqlQuery('SELECT * FROM User ORDER BY id') - - # Fetch one more to see if there should be a 'next' link - return query.fetch(limit+1, offset) + return model.getEntitiesForLimitAndOffset( + soc.models.user.User, limit, offset=offset, order_by='id') def getUserFromId(id): @@ -132,16 +132,8 @@ possibly None if query had no results or neither id or link_name were supplied. """ - if id: - query = db.GqlQuery("SELECT * FROM User WHERE id > :1", id) - return query.fetch(1) - - #if id not supplied, try link name. - if link_name: - query = db.GqlQuery("SELECT * FROM User WHERE link_name > :1", link_name) - return query.fetch(1) - - return None + return model.getNearestEntities( + soc.models.user.User, [('id', id), ('link_name', link_name)]) def findNearestUsersOffset(width, id=None, link_name=None): @@ -160,35 +152,8 @@ None if there are no nearest Users or the offset of the beginning of the range cannot be found for some reason """ - # find User "nearest" to supplied id (Google Account) or link_name - nearest_users = getNearestUsers(id=id, link_name=link_name) - - if not nearest_users: - # no "nearest" User, so indicate that with None - return None - - nearest_user = nearest_users[0] - - # start search for beginning of nearest Users range at offset zero - offset = 0 - users = getUsersForOffsetAndLimit(offset=offset, limit=width) - - while True: - for user in users: - if nearest_user.id == user.id: - # nearest User found in current search range, so return a range start - return max(0, (offset - (width/2))) - - offset = offset + 1 - - # nearest User was not in the current search range, so fetch the next set - users = getUsersForOffsetAndLimit(offset=offset, limit=width) - - if not users: - # nearest User never found, so indicate that with None - break - - return None + return model.findNearestEntitiesOffset( + width, soc.models.user.User, [('id', id), ('link_name', link_name)]) def doesUserExist(id): diff -r 52a42831d9d6 -r 9b39d93b677f app/soc/logic/sponsor.py --- a/app/soc/logic/sponsor.py Thu Oct 02 20:22:15 2008 +0000 +++ b/app/soc/logic/sponsor.py Fri Oct 03 01:32:34 2008 +0000 @@ -99,17 +99,16 @@ return key_name.nameSponsor(link_name) -def getSponsorsForOffsetAndLimit(offset=0, limit=0): +def getSponsorsForLimitAndOffset(limit, offset=0): """Returns Sponsors entities for given offset and limit or None if not found. Args: - offset: offset in entities list which defines first entity to return limit: max amount of entities to return + offset: optional offset in entities list which defines first entity to + return; default is zero (first entity) """ query = soc.models.sponsor.Sponsor.all() - - # Fetch one more to see if there should be a 'next' link - return query.fetch(limit+1, offset) + return query.fetch(limit, offset) def updateOrCreateSponsorFromLinkName(sponsor_link_name, **sponsor_properties): diff -r 52a42831d9d6 -r 9b39d93b677f app/soc/logic/works.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/app/soc/logic/works.py Fri Oct 03 01:32:34 2008 +0000 @@ -0,0 +1,45 @@ +#!/usr/bin/python2.5 +# +# Copyright 2008 the Melange authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Works (Model) query functions. +""" + +__authors__ = [ + '"Todd Larsen" ', + ] + + +from google.appengine.ext import db + +from soc.logic import model + +import soc.models.work + + +def getWorksForLimitAndOffset(limit, offset=0, cls=soc.models.work.Work): + """Returns Works for given offset and limit or None if not found. + + Args: + limit: max amount of entities to return + offset: optional offset in entities list which defines first entity to + return; default is zero (first entity) + cls: Model class of items to return (including sub-classes of that type); + default is Work + """ + query = db.GqlQuery(model.buildOrderedQueryString( + soc.models.work.Work, derived_class=cls, order_by='title')) + + return query.fetch(limit, offset) diff -r 52a42831d9d6 -r 9b39d93b677f app/soc/views/site/docs/list.py --- a/app/soc/views/site/docs/list.py Thu Oct 02 20:22:15 2008 +0000 +++ b/app/soc/views/site/docs/list.py Fri Oct 03 01:32:34 2008 +0000 @@ -22,7 +22,7 @@ ] -from soc.logic import document +from soc.logic import works from soc.views import simple from soc.views.helpers import list_helpers from soc.views.helpers import response_helpers @@ -56,9 +56,14 @@ limit = request.GET.get('limit') offset, limit = list_helpers.getListParemeters(offset=offset, limit=limit) - - docs = document.getWorksForOffsetAndLimit( - offset=offset, limit=limit, cls=soc.models.document.Document) + + # Fetch one more to see if there should be a 'next' link + docs = works.getWorksForLimitAndOffset( + limit + 1, offset=offset, cls=soc.models.document.Document) + + # TODO(tlarsen): uncomment when pagination select control is working. + # form = list_helpers.makeSelectNumItemsForm(request, limit) + # context['form'] = form list_templates = {'list_main': 'soc/list/list_main.html', 'list_pagination': 'soc/list/list_pagination.html', diff -r 52a42831d9d6 -r 9b39d93b677f app/soc/views/site/sponsor/list.py --- a/app/soc/views/site/sponsor/list.py Thu Oct 02 20:22:15 2008 +0000 +++ b/app/soc/views/site/sponsor/list.py Fri Oct 03 01:32:34 2008 +0000 @@ -46,7 +46,12 @@ offset, limit = list_helpers.getListParemeters(offset, limit) - sponsors = sponsor.getSponsorsForOffsetAndLimit(offset, limit) + # Fetch one more to see if there should be a 'next' link + sponsors = sponsor.getSponsorsForLimitAndOffset(limit + 1, offset=offset) + + # TODO(tlarsen): uncomment when pagination select control is working. + # form = list_helpers.makeSelectNumItemsForm(request, limit) + # context['form'] = form list_templates = {'list_main': 'soc/list/list_main.html', 'list_pagination': 'soc/list/list_pagination.html', diff -r 52a42831d9d6 -r 9b39d93b677f app/soc/views/site/user/list.py --- a/app/soc/views/site/user/list.py Thu Oct 02 20:22:15 2008 +0000 +++ b/app/soc/views/site/user/list.py Fri Oct 03 01:32:34 2008 +0000 @@ -29,6 +29,7 @@ import soc.models.user + DEF_SITE_USER_LIST_ALL_TMPL = 'soc/site/user/list/all.html' def all(request, template=DEF_SITE_USER_LIST_ALL_TMPL): @@ -55,8 +56,13 @@ limit = request.GET.get('limit') offset, limit = list_helpers.getListParemeters(offset=offset, limit=limit) - - users = id_user.getUsersForOffsetAndLimit(offset=offset, limit=limit) + + # Fetch one more to see if there should be a 'next' link + users = id_user.getUsersForLimitAndOffset(limit + 1, offset=offset) + + # TODO(tlarsen): uncomment when pagination select control is working. + # form = list_helpers.makeSelectNumItemsForm(request, limit) + # context['form'] = form list_templates = {'list_main': 'soc/list/list_main.html', 'list_pagination': 'soc/list/list_pagination.html', @@ -68,4 +74,4 @@ offset=offset, limit=limit, list_templates=list_templates) return response_helpers.respond(request, template, context) - \ No newline at end of file +