Merge lp:~wgrant/launchpad/bugsummary-v2-apg-app into lp:launchpad

Proposed by William Grant
Status: Merged
Approved by: Curtis Hovey
Approved revision: no longer in the source branch.
Merged at revision: 15691
Proposed branch: lp:~wgrant/launchpad/bugsummary-v2-apg-app
Merge into: lp:launchpad
Diff against target: 572 lines (+202/-138)
5 files modified
lib/lp/bugs/doc/bugsummary.txt (+107/-78)
lib/lp/bugs/model/bug.py (+13/-21)
lib/lp/bugs/model/bugsummary.py (+61/-3)
lib/lp/bugs/model/bugtask.py (+11/-21)
lib/lp/bugs/model/tests/test_bugsummary.py (+10/-15)
To merge this branch: bzr merge lp:~wgrant/launchpad/bugsummary-v2-apg-app
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+116594@code.launchpad.net

Commit message

Teach model and test code about BugSummary.access_policy.

Description of the change

BugSummary privacy is a little special. Public bugs show up in a single row with viewed_by=None, private bugs in several rows with viewed_by=subscriber, one for each subscriber. This obviously isn't quite compatible with the new sharing model, which also grants visibility through access policies, not requiring a direct subscription. So BugSummary has grown a new column, access_policy. For public bugs there'll still be just a single row with viewed_by=None,access_policy=None, but private bugs will expand to a one row for each subscriber, plus one for each access policy.

This branch teaches the application code about the new column, removing the last blocker for lp:~wgrant/launchpad/bugsummary-v2-apg-db, which alters the triggers to start setting it. The two production queries and one test query that filter on viewed_by have been adjusted to use a factored out filter method, which knows about access policies. I've verified that they still all perform excellently.

Tests are a bit of an issue, as it can't really be tested until the DB changes are deployed as well. But the query is factored out and well tested.

I also reworked bugsummary.txt a bit. The new column makes the printed tables a bit wide, so I made privacy display optional.

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
=== modified file 'lib/lp/bugs/doc/bugsummary.txt'
--- lib/lp/bugs/doc/bugsummary.txt 2012-07-11 22:31:52 +0000
+++ lib/lp/bugs/doc/bugsummary.txt 2012-07-25 08:05:24 +0000
@@ -19,9 +19,9 @@
19First we should setup some helpers to use in the examples. These will19First we should setup some helpers to use in the examples. These will
20let us dump the BugSummary table in a readable format.20let us dump the BugSummary table in a readable format.
2121
22 ---------------------------------------------------------------22 ----------------------------------------------------------
23 prod ps dist ds spn tag mile status import pa vis #23 prod ps dist ds spn tag mile status import pa #
24 ---------------------------------------------------------------24 ----------------------------------------------------------
2525
26The columns are product, productseries, distribution, distroseries,26The columns are product, productseries, distribution, distroseries,
27sourcepackagename, tag, milestone, status, importance, has_patch,27sourcepackagename, tag, milestone, status, importance, has_patch,
@@ -40,7 +40,18 @@
40 ... return 'x'40 ... return 'x'
41 ... return object_or_none.name41 ... return object_or_none.name
4242
43 >>> def print_result(bugsummary_resultset):43 >>> def ap_desc(policy_or_none):
44 ... if policy_or_none is None:
45 ... return 'x'
46 ... type_names = {
47 ... InformationType.PRIVATESECURITY: 'se',
48 ... InformationType.USERDATA: 'pr',
49 ... InformationType.PROPRIETARY: 'pp',
50 ... }
51 ... return '%-4s/%-2s' % (
52 ... policy_or_none.pillar.name, type_names[policy_or_none.type])
53
54 >>> def print_result(bugsummary_resultset, include_privacy=False):
44 ... # First, flush and invalidate the cache so we see the effects55 ... # First, flush and invalidate the cache so we see the effects
45 ... # of the underlying database triggers. Normally you don't want56 ... # of the underlying database triggers. Normally you don't want
46 ... # to bother with this as you are only interested in counts of57 ... # to bother with this as you are only interested in counts of
@@ -59,18 +70,28 @@
59 ... BugSummary.sourcepackagename_id, BugSummary.tag,70 ... BugSummary.sourcepackagename_id, BugSummary.tag,
60 ... BugSummary.milestone_id, BugSummary.status,71 ... BugSummary.milestone_id, BugSummary.status,
61 ... BugSummary.importance, BugSummary.has_patch,72 ... BugSummary.importance, BugSummary.has_patch,
62 ... BugSummary.viewed_by_id, BugSummary.id)73 ... BugSummary.viewed_by_id, BugSummary.access_policy_id,
74 ... BugSummary.id)
63 ... fmt = (75 ... fmt = (
64 ... "%-4s %-4s %-4s %-4s %-5s %-3s %-4s "76 ... "%-4s %-4s %-4s %-4s %-5s %-3s %-4s "
65 ... "%-6s %-6s %-2s %-4s %3s")77 ... "%-6s %-6s %-2s")
66 ... header = fmt % (78 ... titles = (
67 ... 'prod', 'ps', 'dist', 'ds', 'spn', 'tag', 'mile',79 ... 'prod', 'ps', 'dist', 'ds', 'spn', 'tag', 'mile',
68 ... 'status', 'import', 'pa', 'vis', '#')80 ... 'status', 'import', 'pa')
81 ... if include_privacy:
82 ... fmt += ' %-4s %-7s'
83 ... titles += ('gra', 'pol')
84 ... fmt += ' %3s'
85 ... titles += ('#',)
86 ... header = fmt % titles
69 ... print "-" * len(header)87 ... print "-" * len(header)
70 ... print header88 ... print header
71 ... print "-" * len(header)89 ... print "-" * len(header)
72 ... for bugsummary in ordered_results:90 ... for bugsummary in ordered_results:
73 ... print fmt % (91 ... if not include_privacy:
92 ... assert bugsummary.viewed_by is None
93 ... assert bugsummary.access_policy is None
94 ... data = (
74 ... name(bugsummary.product),95 ... name(bugsummary.product),
75 ... name(bugsummary.productseries),96 ... name(bugsummary.productseries),
76 ... name(bugsummary.distribution),97 ... name(bugsummary.distribution),
@@ -80,9 +101,13 @@
80 ... name(bugsummary.milestone),101 ... name(bugsummary.milestone),
81 ... str(bugsummary.status)[:6],102 ... str(bugsummary.status)[:6],
82 ... str(bugsummary.importance)[:6],103 ... str(bugsummary.importance)[:6],
83 ... str(bugsummary.has_patch)[:1],104 ... str(bugsummary.has_patch)[:1])
84 ... name(bugsummary.viewed_by),105 ... if include_privacy:
85 ... bugsummary.count)106 ... data += (
107 ... name(bugsummary.viewed_by),
108 ... ap_desc(bugsummary.access_policy),
109 ... )
110 ... print fmt % (data + (bugsummary.count,))
86 ... print " " * (len(header) - 4),111 ... print " " * (len(header) - 4),
87 ... print "==="112 ... print "==="
88 ... sum = bugsummary_resultset.sum(BugSummary.count)113 ... sum = bugsummary_resultset.sum(BugSummary.count)
@@ -90,8 +115,9 @@
90 ... print "%3s" % sum115 ... print "%3s" % sum
91116
92 >>> def print_find(*bs_query_args, **bs_query_kw):117 >>> def print_find(*bs_query_args, **bs_query_kw):
118 ... include_privacy = bs_query_kw.pop('include_privacy', False)
93 ... resultset = store.find(BugSummary, *bs_query_args, **bs_query_kw)119 ... resultset = store.find(BugSummary, *bs_query_args, **bs_query_kw)
94 ... print_result(resultset)120 ... print_result(resultset, include_privacy=include_privacy)
95121
96122
97/!\ A Note About Privacy in These Examples123/!\ A Note About Privacy in These Examples
@@ -119,12 +145,12 @@
119 ... BugSummary.tag == None)145 ... BugSummary.tag == None)
120146
121 >>> print_result(bug_summaries)147 >>> print_result(bug_summaries)
122 ------------------------------------------------------------148 -------------------------------------------------------
123 prod ps dist ds spn tag mile status import pa vis #149 prod ps dist ds spn tag mile status import pa #
124 ------------------------------------------------------------150 -------------------------------------------------------
125 pr-a x x x x x x New Undeci F x 1151 pr-a x x x x x x New Undeci F 1
126 ===152 ===
127 1153 1
128154
129There is one row per tag per combination of product, status and milestone.155There is one row per tag per combination of product, status and milestone.
130If we are interested in all bugs targeted to a product regardless of how156If we are interested in all bugs targeted to a product regardless of how
@@ -145,13 +171,13 @@
145 ... BugSummary.product == prod_a,171 ... BugSummary.product == prod_a,
146 ... BugSummary.tag == None,172 ... BugSummary.tag == None,
147 ... BugSummary.viewed_by == None)173 ... BugSummary.viewed_by == None)
148 ------------------------------------------------------------174 -------------------------------------------------------
149 prod ps dist ds spn tag mile status import pa vis #175 prod ps dist ds spn tag mile status import pa #
150 ------------------------------------------------------------176 -------------------------------------------------------
151 pr-a x x x x x x New Undeci F x 2177 pr-a x x x x x x New Undeci F 2
152 pr-a x x x x x x Confir Undeci F x 2178 pr-a x x x x x x Confir Undeci F 2
153 ===179 ===
154 4180 4
155181
156Here are the rows associated with the 't-a' tag. There is 1 Confirmed182Here are the rows associated with the 't-a' tag. There is 1 Confirmed
157bug task targetted to the pr-a product who's bug is tagged 't-a'.:183bug task targetted to the pr-a product who's bug is tagged 't-a'.:
@@ -160,12 +186,12 @@
160 ... BugSummary.product == prod_a,186 ... BugSummary.product == prod_a,
161 ... BugSummary.tag == u't-a',187 ... BugSummary.tag == u't-a',
162 ... BugSummary.viewed_by == None)188 ... BugSummary.viewed_by == None)
163 ------------------------------------------------------------189 -------------------------------------------------------
164 prod ps dist ds spn tag mile status import pa vis #190 prod ps dist ds spn tag mile status import pa #
165 ------------------------------------------------------------191 -------------------------------------------------------
166 pr-a x x x x t-a x Confir Undeci F x 1192 pr-a x x x x t-a x Confir Undeci F 1
167 ===193 ===
168 1194 1
169195
170You will normally want to get the total count counted in the database196You will normally want to get the total count counted in the database
171rather than waste transmission time to calculate the rows client side.197rather than waste transmission time to calculate the rows client side.
@@ -205,17 +231,17 @@
205 >>> print_find(231 >>> print_find(
206 ... BugSummary.product == prod_a,232 ... BugSummary.product == prod_a,
207 ... BugSummary.viewed_by == None)233 ... BugSummary.viewed_by == None)
208 ------------------------------------------------------------234 -------------------------------------------------------
209 prod ps dist ds spn tag mile status import pa vis #235 prod ps dist ds spn tag mile status import pa #
210 ------------------------------------------------------------236 -------------------------------------------------------
211 pr-a x x x x t-a x Confir Undeci F x 1237 pr-a x x x x t-a x Confir Undeci F 1
212 pr-a x x x x t-b ms-a New Undeci F x 1238 pr-a x x x x t-b ms-a New Undeci F 1
213 pr-a x x x x t-c ms-a New Undeci F x 1239 pr-a x x x x t-c ms-a New Undeci F 1
214 pr-a x x x x x ms-a New Undeci F x 1240 pr-a x x x x x ms-a New Undeci F 1
215 pr-a x x x x x x New Undeci F x 2241 pr-a x x x x x x New Undeci F 2
216 pr-a x x x x x x Confir Undeci F x 2242 pr-a x x x x x x Confir Undeci F 2
217 ===243 ===
218 8244 8
219245
220Number of New bugs not targeted to a milestone. Note the difference246Number of New bugs not targeted to a milestone. Note the difference
221between selecting records where tag is None, and where milestone is None:247between selecting records where tag is None, and where milestone is None:
@@ -269,13 +295,13 @@
269 ... BugSummary.productseries == productseries_b,295 ... BugSummary.productseries == productseries_b,
270 ... BugSummary.product == prod_b),296 ... BugSummary.product == prod_b),
271 ... BugSummary.viewed_by == None)297 ... BugSummary.viewed_by == None)
272 ------------------------------------------------------------298 -------------------------------------------------------
273 prod ps dist ds spn tag mile status import pa vis #299 prod ps dist ds spn tag mile status import pa #
274 ------------------------------------------------------------300 -------------------------------------------------------
275 pr-b x x x x x x New Undeci F x 1301 pr-b x x x x x x New Undeci F 1
276 x ps-b x x x x x New Undeci F x 1302 x ps-b x x x x x New Undeci F 1
277 ===303 ===
278 2304 2
279305
280Distribution Bug Counts306Distribution Bug Counts
281-----------------------307-----------------------
@@ -297,14 +323,14 @@
297 >>> print_find(323 >>> print_find(
298 ... BugSummary.distribution == distribution,324 ... BugSummary.distribution == distribution,
299 ... BugSummary.viewed_by == None)325 ... BugSummary.viewed_by == None)
300 ------------------------------------------------------------326 -------------------------------------------------------
301 prod ps dist ds spn tag mile status import pa vis #327 prod ps dist ds spn tag mile status import pa #
302 ------------------------------------------------------------328 -------------------------------------------------------
303 x x di-a x sp-a x x New Undeci F x 1329 x x di-a x sp-a x x New Undeci F 1
304 x x di-a x x x x New Undeci F x 1330 x x di-a x x x x New Undeci F 1
305 x x di-a x x x x Confir Undeci F x 1331 x x di-a x x x x Confir Undeci F 1
306 ===332 ===
307 3333 3
308334
309How many bugs targeted to a distribution?335How many bugs targeted to a distribution?
310336
@@ -369,12 +395,12 @@
369 >>> print_find(395 >>> print_find(
370 ... BugSummary.distroseries == series_c,396 ... BugSummary.distroseries == series_c,
371 ... BugSummary.viewed_by == None)397 ... BugSummary.viewed_by == None)
372 ------------------------------------------------------------398 -------------------------------------------------------
373 prod ps dist ds spn tag mile status import pa vis #399 prod ps dist ds spn tag mile status import pa #
374 ------------------------------------------------------------400 -------------------------------------------------------
375 x x x ds-c x x x New Undeci F x 1401 x x x ds-c x x x New Undeci F 1
376 ===402 ===
377 1403 1
378404
379405
380Privacy406Privacy
@@ -440,19 +466,19 @@
440 >>> distro_or_series = Or(466 >>> distro_or_series = Or(
441 ... BugSummary.distribution == distro_p,467 ... BugSummary.distribution == distro_p,
442 ... BugSummary.distroseries == series_p)468 ... BugSummary.distroseries == series_p)
443 >>> print_find(distro_or_series)469 >>> print_find(distro_or_series, include_privacy=True)
444 ------------------------------------------------------------470 --------------------------------------------------------------------
445 prod ps dist ds spn tag mile status import pa vis #471 prod ps dist ds spn tag mile status import pa gra pol #
446 ------------------------------------------------------------472 --------------------------------------------------------------------
447 x x di-p x x x x New Undeci F p-b 1473 x x di-p x x x x New Undeci F p-b x 1
448 x x di-p x x x x New Undeci F own 3474 x x di-p x x x x New Undeci F own x 3
449 x x di-p x x x x New Undeci F t-a 1475 x x di-p x x x x New Undeci F t-a x 1
450 x x di-p x x x x New Undeci F t-c 1476 x x di-p x x x x New Undeci F t-c x 1
451 x x di-p x x x x New Undeci F x 1477 x x di-p x x x x New Undeci F x x 1
452 x x x ds-p x x x New Undeci F own 1478 x x x ds-p x x x New Undeci F own x 1
453 x x x ds-p x x x New Undeci F t-c 1479 x x x ds-p x x x New Undeci F t-c x 1
454 ===480 ===
455 9481 9
456482
457So how many public bugs are there on the distro?483So how many public bugs are there on the distro?
458484
@@ -460,12 +486,14 @@
460 ... BugSummary,486 ... BugSummary,
461 ... BugSummary.distribution == distro_p,487 ... BugSummary.distribution == distro_p,
462 ... BugSummary.viewed_by == None, # Public bugs only488 ... BugSummary.viewed_by == None, # Public bugs only
489 ... BugSummary.access_policy == None, # Public bugs only
463 ... BugSummary.sourcepackagename == None,490 ... BugSummary.sourcepackagename == None,
464 ... BugSummary.tag == None).sum(BugSummary.count) or 0491 ... BugSummary.tag == None).sum(BugSummary.count) or 0
465 1492 1
466493
467But how many can the owner see?494But how many can the owner see?
468495
496 >>> from storm.expr import And
469 >>> join = LeftJoin(497 >>> join = LeftJoin(
470 ... BugSummary, TeamParticipation,498 ... BugSummary, TeamParticipation,
471 ... BugSummary.viewed_by_id == TeamParticipation.teamID)499 ... BugSummary.viewed_by_id == TeamParticipation.teamID)
@@ -473,7 +501,8 @@
473 ... BugSummary,501 ... BugSummary,
474 ... BugSummary.distribution == distro_p,502 ... BugSummary.distribution == distro_p,
475 ... Or(503 ... Or(
476 ... BugSummary.viewed_by == None,504 ... And(BugSummary.viewed_by == None,
505 ... BugSummary.access_policy == None),
477 ... TeamParticipation.person == owner),506 ... TeamParticipation.person == owner),
478 ... BugSummary.sourcepackagename == None,507 ... BugSummary.sourcepackagename == None,
479 ... BugSummary.tag == None).sum(BugSummary.count) or 0508 ... BugSummary.tag == None).sum(BugSummary.count) or 0
480509
=== modified file 'lib/lp/bugs/model/bug.py'
--- lib/lp/bugs/model/bug.py 2012-07-19 04:40:03 +0000
+++ lib/lp/bugs/model/bug.py 2012-07-25 08:05:24 +0000
@@ -227,12 +227,7 @@
227 get_property_cache,227 get_property_cache,
228 )228 )
229from lp.services.webapp.authorization import check_permission229from lp.services.webapp.authorization import check_permission
230from lp.services.webapp.interfaces import (230from lp.services.webapp.interfaces import ILaunchBag
231 DEFAULT_FLAVOR,
232 ILaunchBag,
233 IStoreSelector,
234 MAIN_STORE,
235 )
236231
237232
238_bug_tag_query_template = """233_bug_tag_query_template = """
@@ -291,29 +286,26 @@
291 (and {} returned).286 (and {} returned).
292 """287 """
293 # Circular fail.288 # Circular fail.
294 from lp.bugs.model.bugsummary import BugSummary289 from lp.bugs.model.bugsummary import (
290 BugSummary,
291 get_bugsummary_filter_for_user,
292 )
295 tags = {}293 tags = {}
296 if include_tags:294 if include_tags:
297 tags = dict((tag, 0) for tag in include_tags)295 tags = dict((tag, 0) for tag in include_tags)
298 store = getUtility(IStoreSelector).get(MAIN_STORE, DEFAULT_FLAVOR)
299 admin_team = getUtility(ILaunchpadCelebrities).admin
300 if user is not None and not user.inTeam(admin_team):
301 store = store.with_(SQL(
302 "teams AS ("
303 "SELECT team from TeamParticipation WHERE person=?)", (user.id,)))
304 where_conditions = [296 where_conditions = [
305 BugSummary.status.is_in(UNRESOLVED_BUGTASK_STATUSES),297 BugSummary.status.is_in(UNRESOLVED_BUGTASK_STATUSES),
306 BugSummary.tag != None,298 BugSummary.tag != None,
307 context_condition,299 context_condition,
308 ]300 ]
309 if user is None:301
310 where_conditions.append(BugSummary.viewed_by_id == None)302 # Apply the privacy filter.
311 elif not user.inTeam(admin_team):303 store = IStore(BugSummary)
312 where_conditions.append(304 user_with, user_where = get_bugsummary_filter_for_user(user)
313 Or(305 if user_with:
314 BugSummary.viewed_by_id == None,306 store = store.with_(user_with)
315 BugSummary.viewed_by_id.is_in(SQL("SELECT team FROM teams"))307 where_conditions.extend(user_where)
316 ))308
317 sum_count = Sum(BugSummary.count)309 sum_count = Sum(BugSummary.count)
318 tag_count_columns = (BugSummary.tag, sum_count)310 tag_count_columns = (BugSummary.tag, sum_count)
319311
320312
=== modified file 'lib/lp/bugs/model/bugsummary.py'
--- lib/lp/bugs/model/bugsummary.py 2012-06-25 09:45:56 +0000
+++ lib/lp/bugs/model/bugsummary.py 2012-07-25 08:05:24 +0000
@@ -7,16 +7,23 @@
7__all__ = [7__all__ = [
8 'BugSummary',8 'BugSummary',
9 'CombineBugSummaryConstraint',9 'CombineBugSummaryConstraint',
10 'get_bugsummary_filter_for_user',
10 ]11 ]
1112
12from storm.locals import (13from storm.base import Storm
14from storm.expr import (
13 And,15 And,
16 Or,
17 Select,
18 SQL,
19 With,
20 )
21from storm.properties import (
14 Bool,22 Bool,
15 Int,23 Int,
16 Reference,
17 Storm,
18 Unicode,24 Unicode,
19 )25 )
26from storm.references import Reference
20from zope.interface import implements27from zope.interface import implements
21from zope.security.proxy import removeSecurityProxy28from zope.security.proxy import removeSecurityProxy
2229
@@ -29,6 +36,11 @@
29 BugTaskStatus,36 BugTaskStatus,
30 BugTaskStatusSearch,37 BugTaskStatusSearch,
31 )38 )
39from lp.registry.interfaces.role import IPersonRoles
40from lp.registry.model.accesspolicy import (
41 AccessPolicy,
42 AccessPolicyGrant,
43 )
32from lp.registry.model.distribution import Distribution44from lp.registry.model.distribution import Distribution
33from lp.registry.model.distroseries import DistroSeries45from lp.registry.model.distroseries import DistroSeries
34from lp.registry.model.milestone import Milestone46from lp.registry.model.milestone import Milestone
@@ -36,6 +48,7 @@
36from lp.registry.model.product import Product48from lp.registry.model.product import Product
37from lp.registry.model.productseries import ProductSeries49from lp.registry.model.productseries import ProductSeries
38from lp.registry.model.sourcepackagename import SourcePackageName50from lp.registry.model.sourcepackagename import SourcePackageName
51from lp.registry.model.teammembership import TeamParticipation
39from lp.services.database.enumcol import EnumCol52from lp.services.database.enumcol import EnumCol
4053
4154
@@ -76,6 +89,8 @@
7689
77 viewed_by_id = Int(name='viewed_by')90 viewed_by_id = Int(name='viewed_by')
78 viewed_by = Reference(viewed_by_id, Person.id)91 viewed_by = Reference(viewed_by_id, Person.id)
92 access_policy_id = Int(name='access_policy')
93 access_policy = Reference(access_policy_id, AccessPolicy.id)
7994
80 has_patch = Bool()95 has_patch = Bool()
8196
@@ -99,3 +114,46 @@
99 def getBugSummaryContextWhereClause(self):114 def getBugSummaryContextWhereClause(self):
100 """See `IBugSummaryDimension`."""115 """See `IBugSummaryDimension`."""
101 return And(*self.dimensions)116 return And(*self.dimensions)
117
118
119def get_bugsummary_filter_for_user(user):
120 """Build a Storm expression to filter BugSummary by visibility.
121
122 :param user: The user for which visible rows should be calculated.
123 :return: (with_clauses, where_clauses)
124 """
125 # Admins get to see every bug, everyone else only sees bugs
126 # viewable by them-or-their-teams.
127 # Note that because admins can see every bug regardless of
128 # subscription they will see rather inflated counts. Admins get to
129 # deal.
130 public_filter = And(
131 BugSummary.viewed_by_id == None,
132 BugSummary.access_policy_id == None)
133 if user is None:
134 return [], [public_filter]
135 elif IPersonRoles(user).in_admin:
136 return [], []
137 else:
138 with_clauses = [
139 With(
140 'teams',
141 Select(
142 TeamParticipation.teamID, tables=[TeamParticipation],
143 where=(TeamParticipation.personID == user.id))),
144 With(
145 'policies',
146 Select(
147 AccessPolicyGrant.policy_id,
148 tables=[AccessPolicyGrant],
149 where=(
150 AccessPolicyGrant.grantee_id.is_in(
151 SQL("SELECT team FROM teams"))))),
152 ]
153 where_clauses = [Or(
154 public_filter,
155 BugSummary.viewed_by_id.is_in(
156 SQL("SELECT team FROM teams")),
157 BugSummary.access_policy_id.is_in(
158 SQL("SELECT policy FROM policies")))]
159 return with_clauses, where_clauses
102160
=== modified file 'lib/lp/bugs/model/bugtask.py'
--- lib/lp/bugs/model/bugtask.py 2012-07-24 10:03:32 +0000
+++ lib/lp/bugs/model/bugtask.py 2012-07-25 08:05:24 +0000
@@ -1549,7 +1549,10 @@
1549 def countBugs(self, user, contexts, group_on):1549 def countBugs(self, user, contexts, group_on):
1550 """See `IBugTaskSet`."""1550 """See `IBugTaskSet`."""
1551 # Circular fail.1551 # Circular fail.
1552 from lp.bugs.model.bugsummary import BugSummary1552 from lp.bugs.model.bugsummary import (
1553 BugSummary,
1554 get_bugsummary_filter_for_user,
1555 )
1553 conditions = []1556 conditions = []
1554 # Open bug statuses1557 # Open bug statuses
1555 conditions.append(1558 conditions.append(
@@ -1577,27 +1580,14 @@
1577 conditions.append(BugSummary.tag == None)1580 conditions.append(BugSummary.tag == None)
1578 else:1581 else:
1579 conditions.append(BugSummary.tag != None)1582 conditions.append(BugSummary.tag != None)
1583
1584 # Apply the privacy filter.
1580 store = IStore(BugSummary)1585 store = IStore(BugSummary)
1581 admin_team = getUtility(ILaunchpadCelebrities).admin1586 user_with, user_where = get_bugsummary_filter_for_user(user)
1582 if user is not None and not user.inTeam(admin_team):1587 if user_with:
1583 # admins get to see every bug, everyone else only sees bugs1588 store = store.with_(user_with)
1584 # viewable by them-or-their-teams.1589 conditions.extend(user_where)
1585 store = store.with_(SQL(1590
1586 "teams AS ("
1587 "SELECT team from TeamParticipation WHERE person=?)",
1588 (user.id,)))
1589 # Note that because admins can see every bug regardless of
1590 # subscription they will see rather inflated counts. Admins get to
1591 # deal.
1592 if user is None:
1593 conditions.append(BugSummary.viewed_by_id == None)
1594 elif not user.inTeam(admin_team):
1595 conditions.append(
1596 Or(
1597 BugSummary.viewed_by_id == None,
1598 BugSummary.viewed_by_id.is_in(
1599 SQL("SELECT team FROM teams"))
1600 ))
1601 sum_count = Sum(BugSummary.count)1591 sum_count = Sum(BugSummary.count)
1602 resultset = store.find(group_on + (sum_count,), *conditions)1592 resultset = store.find(group_on + (sum_count,), *conditions)
1603 resultset.group_by(*group_on)1593 resultset.group_by(*group_on)
16041594
=== modified file 'lib/lp/bugs/model/tests/test_bugsummary.py'
--- lib/lp/bugs/model/tests/test_bugsummary.py 2012-05-24 22:37:33 +0000
+++ lib/lp/bugs/model/tests/test_bugsummary.py 2012-07-25 08:05:24 +0000
@@ -16,10 +16,12 @@
16 BugTaskStatus,16 BugTaskStatus,
17 )17 )
18from lp.bugs.model.bug import BugTag18from lp.bugs.model.bug import BugTag
19from lp.bugs.model.bugsummary import BugSummary19from lp.bugs.model.bugsummary import (
20 BugSummary,
21 get_bugsummary_filter_for_user,
22 )
20from lp.bugs.model.bugtask import BugTask23from lp.bugs.model.bugtask import BugTask
21from lp.registry.enums import InformationType24from lp.registry.enums import InformationType
22from lp.registry.model.teammembership import TeamParticipation
23from lp.services.database.lpstorm import IMasterStore25from lp.services.database.lpstorm import IMasterStore
24from lp.testing import TestCaseWithFactory26from lp.testing import TestCaseWithFactory
25from lp.testing.dbuser import switch_dbuser27from lp.testing.dbuser import switch_dbuser
@@ -41,21 +43,14 @@
4143
42 def getCount(self, person, **kw_find_expr):44 def getCount(self, person, **kw_find_expr):
43 self._maybe_rollup()45 self._maybe_rollup()
4446 store = self.store
45 public_summaries = self.store.find(47 user_with, user_where = get_bugsummary_filter_for_user(person)
46 BugSummary,48 if user_with:
47 BugSummary.viewed_by == None,49 store = store.with_(user_with)
48 **kw_find_expr)50 summaries = store.find(BugSummary, *user_where, **kw_find_expr)
49 private_summaries = self.store.find(
50 BugSummary,
51 BugSummary.viewed_by_id == TeamParticipation.teamID,
52 TeamParticipation.person == person,
53 **kw_find_expr)
54 all_summaries = public_summaries.union(private_summaries, all=True)
55
56 # Note that if there a 0 records found, sum() returns None, but51 # Note that if there a 0 records found, sum() returns None, but
57 # we prefer to return 0 here.52 # we prefer to return 0 here.
58 return all_summaries.sum(BugSummary.count) or 053 return summaries.sum(BugSummary.count) or 0
5954
60 def assertCount(self, count, user=None, **kw_find_expr):55 def assertCount(self, count, user=None, **kw_find_expr):
61 self.assertEqual(count, self.getCount(user, **kw_find_expr))56 self.assertEqual(count, self.getCount(user, **kw_find_expr))