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
--- 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)
--- 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.
--- 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" <pawel.solyga@gmail.com>',
+ '"Todd Larsen" <tlarsen@google.com>',
]
+
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.
--- 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):
--- 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):
--- /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" <tlarsen@google.com>',
+ ]
+
+
+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)
--- 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',
--- 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',
--- 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
+