Merge lp:~cjwatson/launchpad/answers-hide-inactive-projects into lp:launchpad

Proposed by Colin Watson
Status: Merged
Merged at revision: 18481
Proposed branch: lp:~cjwatson/launchpad/answers-hide-inactive-projects
Merge into: lp:launchpad
Diff against target: 134 lines (+32/-6)
3 files modified
lib/lp/answers/doc/questionsets.txt (+5/-1)
lib/lp/answers/model/question.py (+15/-4)
lib/lp/answers/tests/test_question.py (+12/-1)
To merge this branch: bzr merge lp:~cjwatson/launchpad/answers-hide-inactive-projects
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+331981@code.launchpad.net

Commit message

Hide questions on inactive projects from the results of non-pillar-specific searches.

Description of the change

This would have been about 9000 times easier if the question search infrastructure weren't an unholy mess of SQLObject and hand-written SQL, but such is life. I first tried to do this in January 2016 but ran into incomprehensible test failures and gave up; the bit I'd missed back then was the fix to QuestionPersonSearch.getTableJoins.

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
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/doc/questionsets.txt'
2--- lib/lp/answers/doc/questionsets.txt 2015-07-07 12:46:21 +0000
3+++ lib/lp/answers/doc/questionsets.txt 2017-10-07 02:46:42 +0000
4@@ -175,9 +175,12 @@
5 >>> from lp.registry.interfaces.product import IProductSet
6 >>> firefox = getUtility(IProductSet).getByName('firefox')
7 >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
8+ >>> inactive = factory.makeProduct()
9 >>> login('admin@canonical.com')
10 >>> firefox.answers_usage = ServiceUsage.LAUNCHPAD
11 >>> ubuntu.answers_usage = ServiceUsage.LAUNCHPAD
12+ >>> inactive.answers_usage = ServiceUsage.LAUNCHPAD
13+ >>> inactive.active = False
14 >>> transaction.commit()
15
16 This method can be used to retrieve the projects that are the most actively
17@@ -207,6 +210,7 @@
18 ... ('ubuntu', 3),
19 ... ('firefox', 2),
20 ... ('landscape', 1),
21+ ... (inactive.name, 5),
22 ... ))
23
24 A question is created just before the time limit on Launchpad.
25@@ -232,7 +236,7 @@
26 LAUNCHPAD
27
28 # Launchpad is not returned because the question was not asked in
29- # the last 60 days.
30+ # the last 60 days. Inactive projects are not returned either.
31 >>> for project in question_set.getMostActiveProjects():
32 ... print project.displayname
33 Ubuntu
34
35=== modified file 'lib/lp/answers/model/question.py'
36--- lib/lp/answers/model/question.py 2016-08-09 10:43:45 +0000
37+++ lib/lp/answers/model/question.py 2017-10-07 02:46:42 +0000
38@@ -1,4 +1,4 @@
39-# Copyright 2009-2016 Canonical Ltd. This software is licensed under the
40+# Copyright 2009-2017 Canonical Ltd. This software is licensed under the
41 # GNU Affero General Public License version 3 (see the file LICENSE).
42
43 """Question models."""
44@@ -755,7 +755,7 @@
45 LEFT OUTER JOIN Distribution ON (
46 Question.distribution = Distribution.id)
47 WHERE
48- (Product.answers_usage = %s
49+ ((Product.answers_usage = %s AND Product.active)
50 OR Distribution.answers_usage = %s)
51 AND Question.datecreated > (
52 current_timestamp -interval '60 days')
53@@ -920,6 +920,10 @@
54 constraints.append("""
55 Question.product = Product.id AND Product.active AND
56 Product.project = %s""" % sqlvalues(self.projectgroup))
57+ else:
58+ constraints.append("""
59+ ((Question.product = Product.id AND Product.active) OR
60+ Question.product IS NULL)""")
61
62 return constraints
63
64@@ -929,6 +933,8 @@
65 return self.getMessageJoins(self.needs_attention_from)
66 elif self.projectgroup:
67 return self.getProductJoins()
68+ elif not self.product and not self.distribution:
69+ return self.getActivePillarJoins()
70 else:
71 return []
72
73@@ -941,6 +947,8 @@
74 AND QuestionMessage.owner = %s""" % sqlvalues(person))]
75 if self.projectgroup:
76 joins.extend(self.getProductJoins())
77+ elif not self.product and not self.distribution:
78+ joins.extend(self.getActivePillarJoins())
79
80 return joins
81
82@@ -950,6 +958,10 @@
83 return [('JOIN Product '
84 'ON Question.product = Product.id')]
85
86+ def getActivePillarJoins(self):
87+ """Create the joins needed to select constraints on active pillars."""
88+ return [('LEFT OUTER JOIN Product ON Question.product = Product.id')]
89+
90 def getConstraints(self):
91 """Return a list of SQL constraints to use for this search."""
92
93@@ -1176,8 +1188,7 @@
94
95 if QuestionParticipation.COMMENTER in self.participation:
96 message_joins = self.getMessageJoins(self.person)
97- if not set(joins).intersection(set(message_joins)):
98- joins.extend(message_joins)
99+ joins.extend([join for join in message_joins if join not in joins])
100
101 return joins
102
103
104=== modified file 'lib/lp/answers/tests/test_question.py'
105--- lib/lp/answers/tests/test_question.py 2016-08-23 03:49:28 +0000
106+++ lib/lp/answers/tests/test_question.py 2017-10-07 02:46:42 +0000
107@@ -1,4 +1,4 @@
108-# Copyright 2013 Canonical Ltd. This software is licensed under the
109+# Copyright 2013-2017 Canonical Ltd. This software is licensed under the
110 # GNU Affero General Public License version 3 (see the file LICENSE).
111
112 __metaclass__ = type
113@@ -8,6 +8,7 @@
114 from zope.security.interfaces import Unauthorized
115 from zope.security.proxy import removeSecurityProxy
116
117+from lp.answers.interfaces.questioncollection import IQuestionSet
118 from lp.services.worlddata.interfaces.language import ILanguageSet
119 from lp.testing import (
120 admin_logged_in,
121@@ -64,3 +65,13 @@
122 self.factory.makeQuestion(target=inactive)
123 removeSecurityProxy(inactive).active = False
124 self.assertContentEqual([question], group.searchQuestions())
125+
126+ def test_global_with_inactive_products_not_in_results(self):
127+ product = self.factory.makeProduct()
128+ inactive = self.factory.makeProduct()
129+ active_question = self.factory.makeQuestion(target=product)
130+ inactive_question = self.factory.makeQuestion(target=inactive)
131+ removeSecurityProxy(inactive).active = False
132+ questions = list(getUtility(IQuestionSet).searchQuestions())
133+ self.assertIn(active_question, questions)
134+ self.assertNotIn(inactive_question, questions)