launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #18399
[Merge] lp:~cjwatson/launchpad/storm-vocabulary-base into lp:launchpad
Colin Watson has proposed merging lp:~cjwatson/launchpad/storm-vocabulary-base into lp:launchpad.
Commit message:
Add StormVocabularyBase, mostly copied from SQLObjectVocabularyBase, and use it for GitRepositoryVocabulary.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/storm-vocabulary-base/+merge/257655
Add StormVocabularyBase, mostly copied from SQLObjectVocabularyBase, and use it for GitRepositoryVocabulary.
(I ran into this when working on the browser code for Git merge proposals.)
--
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/storm-vocabulary-base into lp:launchpad.
=== modified file 'lib/lp/code/vocabularies/gitrepository.py'
--- lib/lp/code/vocabularies/gitrepository.py 2015-04-13 19:02:15 +0000
+++ lib/lp/code/vocabularies/gitrepository.py 2015-04-28 15:36:55 +0000
@@ -19,17 +19,17 @@
from lp.services.webapp.vocabulary import (
CountableIterator,
IHugeVocabulary,
- SQLObjectVocabularyBase,
+ StormVocabularyBase,
)
-class GitRepositoryVocabulary(SQLObjectVocabularyBase):
+class GitRepositoryVocabulary(StormVocabularyBase):
"""A vocabulary for searching Git repositories."""
implements(IHugeVocabulary)
_table = GitRepository
- _orderBy = ['name', 'id']
+ _order_by = ['name', 'id']
displayname = 'Select a Git repository'
step_title = 'Search'
=== modified file 'lib/lp/services/webapp/marshallers.py'
--- lib/lp/services/webapp/marshallers.py 2010-03-26 19:18:31 +0000
+++ lib/lp/services/webapp/marshallers.py 2015-04-28 15:36:55 +0000
@@ -10,12 +10,13 @@
def choiceMarshallerError(field, request, vocabulary=None):
# We don't support marshalling a normal Choice field with a
- # SQLObjectVocabularyBase-based vocabulary.
+ # SQLObjectVocabularyBase/StormVocabularyBase-based vocabulary.
# Normally for this kind of use case, one returns None and
# lets the Zope machinery alert the user that the lookup has gone wrong.
# However, we want to be more helpful, so we make an assertion,
# with a comment on how to make things better.
raise AssertionError("You exported %s as an IChoice based on an "
- "SQLObjectVocabularyBase, you should use "
- "lazr.restful.fields.ReferenceChoice instead."
+ "SQLObjectVocabularyBase/StormVocabularyBase; you "
+ "should use lazr.restful.fields.ReferenceChoice "
+ "instead."
% field.__name__)
=== modified file 'lib/lp/services/webapp/vocabulary.py'
--- lib/lp/services/webapp/vocabulary.py 2013-01-07 02:40:55 +0000
+++ lib/lp/services/webapp/vocabulary.py 2015-04-28 15:36:55 +0000
@@ -10,14 +10,15 @@
__metaclass__ = type
__all__ = [
+ 'BatchedCountableIterator',
+ 'CountableIterator',
'FilteredVocabularyBase',
'ForgivingSimpleVocabulary',
'IHugeVocabulary',
+ 'NamedSQLObjectHugeVocabulary',
+ 'NamedSQLObjectVocabulary',
'SQLObjectVocabularyBase',
- 'NamedSQLObjectVocabulary',
- 'NamedSQLObjectHugeVocabulary',
- 'CountableIterator',
- 'BatchedCountableIterator',
+ 'StormVocabularyBase',
'VocabularyFilter',
]
@@ -28,6 +29,8 @@
AND,
CONTAINSSTRING,
)
+from storm.base import Storm
+from storm.store import EmptyResultSet
from zope.interface import (
Attribute,
implements,
@@ -43,6 +46,7 @@
)
from zope.security.proxy import isinstance as zisinstance
+from lp.services.database.interfaces import IStore
from lp.services.database.sqlbase import SQLBase
from lp.services.helpers import ensure_unicode
@@ -464,3 +468,120 @@
clause = AND(clause, self._filter)
results = self._table.select(clause, orderBy=self._orderBy)
return self.iterator(results.count(), results, self.toTerm)
+
+
+class StormVocabularyBase(FilteredVocabularyBase):
+ """A base class for widgets that are rendered to collect values
+ for attributes that are Storm references.
+
+ So if a content class behind some form looks like:
+
+ class Foo(StormBase):
+ id = Int(...)
+ bar_id = Int(...)
+ bar = Reference(bar_id, ...)
+ ...
+
+ Then the vocabulary for the widget that captures a value for bar
+ should derive from StormVocabularyBase.
+ """
+ implements(IVocabulary, IVocabularyTokenized)
+ _order_by = None
+ _clauses = []
+
+ def __init__(self, context=None):
+ self.context = context
+
+ # XXX kiko 2007-01-16: note that the method searchForTerms is part of
+ # IHugeVocabulary, and so should not necessarily need to be
+ # implemented here; however, many of our vocabularies depend on
+ # searchForTerms for popup functionality so I have chosen to just do
+ # that. It is possible that a better solution would be to have the
+ # search functionality produce a new vocabulary restricted to the
+ # desired subset.
+ def searchForTerms(self, query=None, vocab_filter=None):
+ results = self.search(query, vocab_filter)
+ return CountableIterator(results.count(), results, self.toTerm)
+
+ def search(self, query, vocab_filter=None):
+ # This default implementation of searchForTerms glues together
+ # the legacy API of search() with the toTerm method. If you
+ # don't reimplement searchForTerms you will need to at least
+ # provide your own search() method.
+ raise NotImplementedError
+
+ def toTerm(self, obj):
+ # This default implementation assumes that your object has a
+ # title attribute. If it does not you will need to reimplement
+ # toTerm, or reimplement the whole searchForTerms.
+ return SimpleTerm(obj, obj.id, obj.title)
+
+ @property
+ def _entries(self):
+ entries = IStore(self._table).find(self._table, *self._clauses)
+ if self._order_by is not None:
+ entries = entries.order_by(self._order_by)
+ return entries
+
+ def __iter__(self):
+ """Return an iterator which provides the terms from the vocabulary."""
+ for obj in self._entries:
+ yield self.toTerm(obj)
+
+ def __len__(self):
+ return self._entries.count()
+
+ def __contains__(self, obj):
+ # Sometimes this method is called with a Storm instance, but z3 form
+ # machinery sends through integer ids. This might be due to a bug
+ # somewhere.
+ if zisinstance(obj, Storm):
+ clauses = [self._table.id == obj.id]
+ if self._clauses:
+ # XXX kiko 2007-01-16: this code is untested.
+ clauses.extend(self._clauses)
+ found_obj = IStore(self._table).find(self._table, *clauses).one()
+ return found_obj is not None and found_obj == obj
+ else:
+ clauses = [self._table.id == int(obj)]
+ if self._clauses:
+ # XXX kiko 2007-01-16: this code is untested.
+ clauses.extend(self._clauses)
+ found_obj = IStore(self._table).find(self._table, *clauses).one()
+ return found_obj is not None
+
+ def getTerm(self, value):
+ # Short circuit. There is probably a design problem here since we
+ # sometimes get the id and sometimes a Storm instance.
+ if zisinstance(value, Storm):
+ return self.toTerm(value)
+
+ try:
+ value = int(value)
+ except ValueError:
+ raise LookupError(value)
+
+ clauses = [self._table.id == value]
+ if self._clauses:
+ clauses.extend(self._clauses)
+ try:
+ obj = IStore(self._table).find(self._table, *clauses).one()
+ except ValueError:
+ raise LookupError(value)
+
+ if obj is None:
+ raise LookupError(value)
+
+ return self.toTerm(obj)
+
+ def getTermByToken(self, token):
+ return self.getTerm(token)
+
+ def emptySelectResults(self):
+ """Return a SelectResults object without any elements.
+
+ This is to be used when no search string is given to the search()
+ method of subclasses, in order to be consistent and always return
+ a SelectResults object.
+ """
+ return EmptyResultSet()
=== modified file 'lib/lp/services/webservice/configure.zcml'
--- lib/lp/services/webservice/configure.zcml 2012-01-31 01:19:45 +0000
+++ lib/lp/services/webservice/configure.zcml 2015-04-28 15:36:55 +0000
@@ -64,12 +64,26 @@
factory="lp.services.webapp.marshallers.choiceMarshallerError"
/>
<adapter
+ for="zope.schema.interfaces.IChoice
+ zope.publisher.interfaces.http.IHTTPRequest
+ lp.services.webapp.vocabulary.StormVocabularyBase"
+ provides="lazr.restful.interfaces.IFieldMarshaller"
+ factory="lp.services.webapp.marshallers.choiceMarshallerError"
+ />
+ <adapter
for="lazr.restful.interfaces.IReferenceChoice
zope.publisher.interfaces.http.IHTTPRequest
lp.services.webapp.vocabulary.SQLObjectVocabularyBase"
provides="lazr.restful.interfaces.IFieldMarshaller"
factory="lazr.restful.marshallers.ObjectLookupFieldMarshaller"
/>
+ <adapter
+ for="lazr.restful.interfaces.IReferenceChoice
+ zope.publisher.interfaces.http.IHTTPRequest
+ lp.services.webapp.vocabulary.StormVocabularyBase"
+ provides="lazr.restful.interfaces.IFieldMarshaller"
+ factory="lazr.restful.marshallers.ObjectLookupFieldMarshaller"
+ />
<!-- The API documentation -->
<browser:page
=== modified file 'lib/lp/services/webservice/doc/webservice-marshallers.txt'
--- lib/lp/services/webservice/doc/webservice-marshallers.txt 2011-12-24 17:49:30 +0000
+++ lib/lp/services/webservice/doc/webservice-marshallers.txt 2015-04-28 15:36:55 +0000
@@ -99,7 +99,7 @@
Traceback (most recent call last):
...
AssertionError: You exported some_person as an IChoice based on an
- SQLObjectVocabularyBase, you should use
+ SQLObjectVocabularyBase/StormVocabularyBase; you should use
lazr.restful.fields.ReferenceChoice instead.
Cleanup.
Follow ups