Merge lp:~jcsackett/launchpad/no-questions-on-disabled-2 into lp:launchpad

Proposed by j.c.sackett
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 16356
Proposed branch: lp:~jcsackett/launchpad/no-questions-on-disabled-2
Merge into: lp:launchpad
Prerequisite: lp:~jcsackett/launchpad/no-questions-on-disabled
Diff against target: 224 lines (+75/-10)
7 files modified
lib/lp/answers/browser/question.py (+10/-3)
lib/lp/answers/configure.zcml (+11/-0)
lib/lp/answers/stories/project-add-question.txt (+2/-2)
lib/lp/answers/tests/test_vocabulary.py (+25/-2)
lib/lp/answers/vocabulary.py (+19/-1)
lib/lp/app/widgets/launchpadtarget.py (+4/-1)
lib/lp/testing/factory.py (+4/-1)
To merge this branch: bzr merge lp:~jcsackett/launchpad/no-questions-on-disabled-2
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+138362@code.launchpad.net

Commit message

Removes products that do not have answers disabled from questiontarget picker results.

Description of the change

Summary
=======
As targets without answers enabled (including private projects) are not valid
question targets, they should not appear in the question target picker when
retargeting a question.

To do this, the vocabulary needs to be modified.

Preimp
======
None. Continuation of work.

Implementation
==============
A new vocabulary, UsesAnswersProductVocabulary, is created that passes an
additional filter to its parent class, ProductVocabulary on search. This
filter removes products that do not have official_answers = True.

This vocabulary is then used in the questiontarget widget.

Ideally, the filter should use answers_usage over official_answers, but using
that for the filter resulted in empty resultsets for reasons I couldn't
adequately explain. As the companion filter,
UsesAnswersDistributionVocabulary, uses official_answers this is at least
consistent.

Tests
=====
bin/test -vvct test_products_not_using_answers_not_found

QA
==
Search for a product without answers setup in the question retargeting picker.
It should not show up in the picker results.

LoC
===
Part of private projects.

Lint
====

Checking for conflicts and issues in changed files.

Linting changed files:
  lib/lp/answers/configure.zcml
  lib/lp/answers/browser/tests/test_question.py
  lib/lp/answers/templates/question-add-search.pt
  lib/lp/answers/tests/test_vocabulary.py
  lib/lp/answers/vocabulary.py
  lib/lp/testing/factory.py
  lib/lp/app/widgets/launchpadtarget.py
  lib/lp/answers/browser/question.py

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'lib/lp/answers/browser/question.py'
2--- lib/lp/answers/browser/question.py 2012-12-10 13:33:31 +0000
3+++ lib/lp/answers/browser/question.py 2012-12-10 13:33:32 +0000
4@@ -76,7 +76,10 @@
5 IAnswersFrontPageSearchForm,
6 IQuestionTarget,
7 )
8-from lp.answers.vocabulary import UsesAnswersDistributionVocabulary
9+from lp.answers.vocabulary import (
10+ UsesAnswersDistributionVocabulary,
11+ UsesAnswersProductVocabulary,
12+ )
13 from lp.app.browser.launchpadform import (
14 action,
15 custom_widget,
16@@ -648,8 +651,9 @@
17 @property
18 def context_uses_answers(self):
19 """Return True if the context uses launchpad as an answer forum."""
20- if IServiceUsage.providedBy(self.context):
21- return self.context.answers_usage == ServiceUsage.LAUNCHPAD
22+ usage = IServiceUsage(self.context)
23+ if usage is not None:
24+ return usage.answers_usage == ServiceUsage.LAUNCHPAD
25 else:
26 return False
27
28@@ -739,6 +743,9 @@
29 class QuestionTargetWidget(LaunchpadTargetWidget):
30 """A targeting widget that is aware of pillars that use Answers."""
31
32+ def getProductVocabulary(self):
33+ return 'UsesAnswersProduct'
34+
35 def getDistributionVocabulary(self):
36 distro = self.context.context.distribution
37 vocabulary = UsesAnswersDistributionVocabulary(distro)
38
39=== modified file 'lib/lp/answers/configure.zcml'
40--- lib/lp/answers/configure.zcml 2012-10-30 16:59:58 +0000
41+++ lib/lp/answers/configure.zcml 2012-12-10 13:33:32 +0000
42@@ -187,6 +187,17 @@
43 <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
44 </securedutility>
45
46+ <securedutility
47+ name="UsesAnswersProduct"
48+ component=".vocabulary.UsesAnswersProductVocabulary"
49+ provides="zope.schema.interfaces.IVocabularyFactory"
50+ >
51+ <allow interface="zope.schema.interfaces.IVocabularyFactory"/>
52+ </securedutility>
53+
54+ <class class=".vocabulary.UsesAnswersProductVocabulary">
55+ <allow interface="lp.services.webapp.vocabulary.IHugeVocabulary"/>
56+ </class>
57 <!-- QuestionSet -->
58 <class class=".model.question.QuestionSet">
59 <allow interface=".interfaces.questioncollection.IQuestionSet" />
60
61=== modified file 'lib/lp/answers/stories/project-add-question.txt'
62--- lib/lp/answers/stories/project-add-question.txt 2012-01-15 11:06:57 +0000
63+++ lib/lp/answers/stories/project-add-question.txt 2012-12-10 13:33:32 +0000
64@@ -198,9 +198,9 @@
65 and expect someone to reply in the same language.
66
67 >>> user_browser.open(
68- ... 'http://answers.launchpad.dev/thunderbird/+addquestion')
69+ ... 'http://answers.launchpad.dev/firefox/+addquestion')
70 >>> print user_browser.getControl('Language').displayOptions
71- ['English (en) *', 'Japanese (ja) *']
72+ ['English (en) *', 'Japanese (ja)']
73
74 The supported languages will not be shown immediately when Sample Person
75 asks a question Thunderbird question from the context of the Mozilla
76
77=== modified file 'lib/lp/answers/tests/test_vocabulary.py'
78--- lib/lp/answers/tests/test_vocabulary.py 2012-01-01 02:58:52 +0000
79+++ lib/lp/answers/tests/test_vocabulary.py 2012-12-10 13:33:32 +0000
80@@ -5,7 +5,11 @@
81
82 __metaclass__ = type
83
84-from lp.answers.vocabulary import UsesAnswersDistributionVocabulary
85+from lp.answers.vocabulary import (
86+ UsesAnswersDistributionVocabulary,
87+ UsesAnswersProductVocabulary,
88+ )
89+from lp.app.enums import ServiceUsage
90 from lp.testing import (
91 person_logged_in,
92 TestCaseWithFactory,
93@@ -15,6 +19,7 @@
94
95 class UsesAnswersDistributionVocabularyTestCase(TestCaseWithFactory):
96 """Test that the vocabulary behaves as expected."""
97+
98 layer = DatabaseFunctionalLayer
99
100 def test_init_with_distribution(self):
101@@ -23,7 +28,7 @@
102 distribution = self.factory.makeDistribution()
103 vocabulary = UsesAnswersDistributionVocabulary(distribution)
104 self.assertEqual(distribution, vocabulary.context)
105- self.assertEqual(distribution, vocabulary.distribution)
106+ self.assertEqual(distribution, vocabulary.distribution)
107
108 def test_init_without_distribution(self):
109 # When the context is not adaptable to IDistribution, the
110@@ -67,3 +72,21 @@
111 self.assertFalse(
112 thing in vocabulary,
113 "Vocabulary contains a non-distribution.")
114+
115+
116+class UsesAnswersProductVocabularyTestCase(TestCaseWithFactory):
117+ """Test that the product vocabulary behaves as expected."""
118+
119+ layer = DatabaseFunctionalLayer
120+
121+ def test_products_not_using_answers_not_found(self):
122+ using_product = self.factory.makeProduct(
123+ name='foobar', answers_usage=ServiceUsage.LAUNCHPAD)
124+ not_using_product = self.factory.makeProduct(
125+ name='foobarbaz', answers_usage=ServiceUsage.NOT_APPLICABLE)
126+ vocabulary = UsesAnswersProductVocabulary()
127+ products = vocabulary.search(query='foobar')
128+ self.assertTrue(using_product in products,
129+ 'Valid product not found in vocabulary.')
130+ self.assertTrue(not_using_product not in products,
131+ 'Vocabulary found a product not using answers.')
132
133=== modified file 'lib/lp/answers/vocabulary.py'
134--- lib/lp/answers/vocabulary.py 2012-01-01 02:58:52 +0000
135+++ lib/lp/answers/vocabulary.py 2012-12-10 13:33:32 +0000
136@@ -7,16 +7,23 @@
137 __all__ = [
138 'FAQVocabulary',
139 'UsesAnswersDistributionVocabulary',
140+ 'UsesAnswersProductVocabulary',
141 ]
142
143 from sqlobject import OR
144+from storm.expr import And
145 from zope.interface import implements
146 from zope.schema.vocabulary import SimpleTerm
147
148+from lp.app.enums import ServiceUsage
149 from lp.answers.interfaces.faq import IFAQ
150 from lp.answers.interfaces.faqtarget import IFAQTarget
151 from lp.registry.interfaces.distribution import IDistribution
152-from lp.registry.vocabularies import DistributionVocabulary
153+from lp.registry.model.product import Product
154+from lp.registry.vocabularies import (
155+ DistributionVocabulary,
156+ ProductVocabulary,
157+ )
158 from lp.services.webapp.vocabulary import (
159 CountableIterator,
160 FilteredVocabularyBase,
161@@ -80,6 +87,17 @@
162 return CountableIterator(results.count(), results, self.toTerm)
163
164
165+class UsesAnswersProductVocabulary(ProductVocabulary):
166+ """Products that use Launchpad to track questions."""
167+
168+ def search(self, query, vocab_filter=None):
169+ if vocab_filter is None:
170+ vocab_filter = []
171+ vocab_filter.append(
172+ And(Product.official_answers == True))
173+ return super(UsesAnswersProductVocabulary, self).search(
174+ query, vocab_filter)
175+
176 class UsesAnswersDistributionVocabulary(DistributionVocabulary):
177 """Distributions that use Launchpad to track questions.
178
179
180=== modified file 'lib/lp/app/widgets/launchpadtarget.py'
181--- lib/lp/app/widgets/launchpadtarget.py 2012-12-10 09:13:35 +0000
182+++ lib/lp/app/widgets/launchpadtarget.py 2012-12-10 13:33:32 +0000
183@@ -53,6 +53,9 @@
184
185 def getDistributionVocabulary(self):
186 return 'Distribution'
187+
188+ def getProductVocabulary(self):
189+ return 'Product'
190
191 def setUpSubWidgets(self):
192 if self._widgets_set_up:
193@@ -60,7 +63,7 @@
194 fields = [
195 Choice(
196 __name__='product', title=u'Project',
197- required=True, vocabulary='Product'),
198+ required=True, vocabulary=self.getProductVocabulary()),
199 Choice(
200 __name__='distribution', title=u"Distribution",
201 required=True, vocabulary=self.getDistributionVocabulary(),
202
203=== modified file 'lib/lp/testing/factory.py'
204--- lib/lp/testing/factory.py 2012-12-05 15:58:18 +0000
205+++ lib/lp/testing/factory.py 2012-12-10 13:33:32 +0000
206@@ -973,7 +973,8 @@
207 title=None, summary=None, official_malone=None,
208 translations_usage=None, bug_supervisor=None, driver=None, icon=None,
209 bug_sharing_policy=None, branch_sharing_policy=None,
210- specification_sharing_policy=None, information_type=None):
211+ specification_sharing_policy=None, information_type=None,
212+ answers_usage=None):
213 """Create and return a new, arbitrary Product."""
214 if owner is None:
215 owner = self.makePerson()
216@@ -1020,6 +1021,8 @@
217 naked_product.official_malone = official_malone
218 if translations_usage is not None:
219 naked_product.translations_usage = translations_usage
220+ if answers_usage is not None:
221+ naked_product.answers_usage = answers_usage
222 if bug_supervisor is not None:
223 naked_product.bug_supervisor = bug_supervisor
224 if driver is not None: