14012.1.2
by Curtis Hovey
Added rudimentary UsesBugsDistributionVocabulary. |
1 |
# Copyright 2011 Canonical Ltd. This software is licensed under the
|
2 |
# GNU Affero General Public License version 3 (see the file LICENSE).
|
|
3 |
||
4 |
"""Bug domain vocabularies"""
|
|
5 |
||
6 |
__metaclass__ = type |
|
14012.1.8
by Curtis Hovey
Created BugTaskTargetWidget which uses UsesBugsDistributionVocabulary. |
7 |
__all__ = [ |
8 |
'UsesBugsDistributionVocabulary', |
|
14435.1.1
by Curtis Hovey
moved bug vocabs to lp.bugs. |
9 |
'BugNominatableDistroSeriesVocabulary', |
10 |
'BugNominatableProductSeriesVocabulary', |
|
11 |
'BugNominatableSeriesVocabulary', |
|
12 |
'BugTrackerVocabulary', |
|
13 |
'BugVocabulary', |
|
14 |
'BugWatchVocabulary', |
|
15 |
'DistributionUsingMaloneVocabulary', |
|
16 |
'project_products_using_malone_vocabulary_factory', |
|
17 |
'UsesBugsDistributionVocabulary', |
|
18 |
'WebBugTrackerVocabulary', |
|
14012.1.8
by Curtis Hovey
Created BugTaskTargetWidget which uses UsesBugsDistributionVocabulary. |
19 |
]
|
14012.1.2
by Curtis Hovey
Added rudimentary UsesBugsDistributionVocabulary. |
20 |
|
14435.1.1
by Curtis Hovey
moved bug vocabs to lp.bugs. |
21 |
import cgi |
22 |
from operator import attrgetter |
|
23 |
||
24 |
from sqlobject import ( |
|
25 |
CONTAINSSTRING, |
|
26 |
OR, |
|
27 |
)
|
|
28 |
from storm.expr import ( |
|
29 |
And, |
|
30 |
Or, |
|
31 |
)
|
|
32 |
from zope.component import getUtility |
|
33 |
from zope.interface import implements |
|
34 |
from zope.schema.interfaces import ( |
|
35 |
IVocabulary, |
|
36 |
IVocabularyTokenized, |
|
37 |
)
|
|
38 |
from zope.schema.vocabulary import ( |
|
39 |
SimpleTerm, |
|
40 |
SimpleVocabulary, |
|
41 |
)
|
|
42 |
||
43 |
from lp.app.browser.stringformatter import FormattersAPI |
|
44 |
from lp.app.enums import ServiceUsage |
|
45 |
from lp.bugs.interfaces.bugtask import IBugTask |
|
46 |
from lp.bugs.interfaces.bugtracker import BugTrackerType |
|
47 |
from lp.bugs.model.bug import Bug |
|
48 |
from lp.bugs.model.bugtracker import BugTracker |
|
49 |
from lp.bugs.model.bugwatch import BugWatch |
|
14012.1.5
by Curtis Hovey
Adapt to a distribution and do not accept a non-distro. |
50 |
from lp.registry.interfaces.distribution import IDistribution |
14435.1.1
by Curtis Hovey
moved bug vocabs to lp.bugs. |
51 |
from lp.registry.interfaces.projectgroup import IProjectGroup |
52 |
from lp.registry.interfaces.series import SeriesStatus |
|
53 |
from lp.registry.model.distribution import Distribution |
|
54 |
from lp.registry.model.distroseries import DistroSeries |
|
55 |
from lp.registry.model.productseries import ProductSeries |
|
14012.1.2
by Curtis Hovey
Added rudimentary UsesBugsDistributionVocabulary. |
56 |
from lp.registry.vocabularies import DistributionVocabulary |
14612.2.1
by William Grant
format-imports on lib/. So many imports. |
57 |
from lp.services.database.lpstorm import IStore |
58 |
from lp.services.helpers import ( |
|
59 |
ensure_unicode, |
|
60 |
shortlist, |
|
61 |
)
|
|
62 |
from lp.services.webapp.interfaces import ILaunchBag |
|
63 |
from lp.services.webapp.vocabulary import ( |
|
64 |
CountableIterator, |
|
65 |
IHugeVocabulary, |
|
66 |
NamedSQLObjectVocabulary, |
|
67 |
SQLObjectVocabularyBase, |
|
68 |
)
|
|
14012.1.2
by Curtis Hovey
Added rudimentary UsesBugsDistributionVocabulary. |
69 |
|
70 |
||
71 |
class UsesBugsDistributionVocabulary(DistributionVocabulary): |
|
14012.1.5
by Curtis Hovey
Adapt to a distribution and do not accept a non-distro. |
72 |
"""Distributions that use Launchpad to track bugs.
|
73 |
||
74 |
If the context is a distribution, it is always included in the
|
|
75 |
vocabulary. Historic data is not invalidated if a distro stops
|
|
76 |
using Launchpad to track bugs. This vocabulary offers the correct
|
|
77 |
choices of distributions at this moment.
|
|
78 |
"""
|
|
79 |
||
80 |
def __init__(self, context=None): |
|
81 |
super(UsesBugsDistributionVocabulary, self).__init__(context=context) |
|
82 |
self.distribution = IDistribution(self.context, None) |
|
14012.1.3
by Curtis Hovey
Constrain UsesBugsDistributionVocabulary to distros that use Lp Bugs. |
83 |
|
84 |
@property
|
|
85 |
def _filter(self): |
|
14012.1.5
by Curtis Hovey
Adapt to a distribution and do not accept a non-distro. |
86 |
if self.distribution is None: |
14012.1.4
by Curtis Hovey
Added the context distribution to the vocab to ensure historic |
87 |
distro_id = 0 |
88 |
else: |
|
14012.1.5
by Curtis Hovey
Adapt to a distribution and do not accept a non-distro. |
89 |
distro_id = self.distribution.id |
14012.1.4
by Curtis Hovey
Added the context distribution to the vocab to ensure historic |
90 |
return OR( |
91 |
self._table.q.official_malone == True, |
|
92 |
self._table.id == distro_id) |
|
14435.1.1
by Curtis Hovey
moved bug vocabs to lp.bugs. |
93 |
|
94 |
||
95 |
class BugVocabulary(SQLObjectVocabularyBase): |
|
96 |
||
97 |
_table = Bug |
|
98 |
_orderBy = 'id' |
|
99 |
||
100 |
||
101 |
class BugTrackerVocabulary(SQLObjectVocabularyBase): |
|
102 |
"""All web and email based external bug trackers."""
|
|
103 |
displayname = 'Select a bug tracker' |
|
104 |
step_title = 'Search' |
|
105 |
implements(IHugeVocabulary) |
|
106 |
_table = BugTracker |
|
107 |
_filter = True |
|
108 |
_orderBy = 'title' |
|
109 |
_order_by = [BugTracker.title] |
|
110 |
||
111 |
def toTerm(self, obj): |
|
112 |
"""See `IVocabulary`."""
|
|
113 |
return SimpleTerm(obj, obj.name, obj.title) |
|
114 |
||
115 |
def getTermByToken(self, token): |
|
116 |
"""See `IVocabularyTokenized`."""
|
|
117 |
result = IStore(self._table).find( |
|
118 |
self._table, |
|
119 |
self._filter, |
|
120 |
BugTracker.name == token).one() |
|
121 |
if result is None: |
|
122 |
raise LookupError(token) |
|
123 |
return self.toTerm(result) |
|
124 |
||
125 |
def search(self, query, vocab_filter=None): |
|
126 |
"""Search for web bug trackers."""
|
|
127 |
query = ensure_unicode(query).lower() |
|
128 |
results = IStore(self._table).find( |
|
129 |
self._table, And( |
|
130 |
self._filter, |
|
131 |
BugTracker.active == True, |
|
132 |
Or( |
|
133 |
CONTAINSSTRING(BugTracker.name, query), |
|
134 |
CONTAINSSTRING(BugTracker.title, query), |
|
135 |
CONTAINSSTRING(BugTracker.summary, query), |
|
136 |
CONTAINSSTRING(BugTracker.baseurl, query)))) |
|
137 |
results = results.order_by(self._order_by) |
|
138 |
return results |
|
139 |
||
140 |
def searchForTerms(self, query=None, vocab_filter=None): |
|
141 |
"""See `IHugeVocabulary`."""
|
|
142 |
results = self.search(query, vocab_filter) |
|
143 |
return CountableIterator(results.count(), results, self.toTerm) |
|
144 |
||
145 |
||
146 |
class WebBugTrackerVocabulary(BugTrackerVocabulary): |
|
147 |
"""All web-based bug tracker types."""
|
|
148 |
_filter = BugTracker.bugtrackertype != BugTrackerType.EMAILADDRESS |
|
149 |
||
150 |
||
151 |
def project_products_using_malone_vocabulary_factory(context): |
|
152 |
"""Return a vocabulary containing a project's products using Malone."""
|
|
153 |
project = IProjectGroup(context) |
|
154 |
return SimpleVocabulary([ |
|
155 |
SimpleTerm(product, product.name, title=product.displayname) |
|
156 |
for product in project.products |
|
157 |
if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD]) |
|
158 |
||
159 |
||
160 |
class BugWatchVocabulary(SQLObjectVocabularyBase): |
|
161 |
_table = BugWatch |
|
162 |
||
163 |
def __iter__(self): |
|
164 |
assert IBugTask.providedBy(self.context), ( |
|
165 |
"BugWatchVocabulary expects its context to be an IBugTask.") |
|
166 |
bug = self.context.bug |
|
167 |
||
168 |
for watch in bug.watches: |
|
169 |
yield self.toTerm(watch) |
|
170 |
||
171 |
def toTerm(self, watch): |
|
172 |
||
173 |
def escape(string): |
|
174 |
return cgi.escape(string, quote=True) |
|
175 |
||
176 |
if watch.url.startswith('mailto:'): |
|
177 |
user = getUtility(ILaunchBag).user |
|
178 |
if user is None: |
|
179 |
title = FormattersAPI( |
|
180 |
watch.bugtracker.title).obfuscate_email() |
|
181 |
return SimpleTerm( |
|
182 |
watch, watch.id, escape(title)) |
|
183 |
else: |
|
184 |
url = watch.url |
|
185 |
title = escape(watch.bugtracker.title) |
|
186 |
if url in title: |
|
187 |
title = title.replace( |
|
188 |
url, '<a href="%s">%s</a>' % ( |
|
189 |
escape(url), escape(url))) |
|
190 |
else: |
|
191 |
title = '%s <<a href="%s">%s</a>>' % ( |
|
192 |
title, escape(url), escape(url[7:])) |
|
193 |
return SimpleTerm(watch, watch.id, title) |
|
194 |
else: |
|
195 |
return SimpleTerm( |
|
196 |
watch, watch.id, '%s <a href="%s">#%s</a>' % ( |
|
197 |
escape(watch.bugtracker.title), |
|
198 |
escape(watch.url), |
|
199 |
escape(watch.remotebug))) |
|
200 |
||
201 |
||
202 |
class DistributionUsingMaloneVocabulary: |
|
203 |
"""All the distributions that uses Malone officially."""
|
|
204 |
||
205 |
implements(IVocabulary, IVocabularyTokenized) |
|
206 |
||
207 |
_orderBy = 'displayname' |
|
208 |
||
209 |
def __init__(self, context=None): |
|
210 |
self.context = context |
|
211 |
||
212 |
def __iter__(self): |
|
213 |
"""Return an iterator which provides the terms from the vocabulary."""
|
|
214 |
distributions_using_malone = Distribution.selectBy( |
|
215 |
official_malone=True, orderBy=self._orderBy) |
|
216 |
for distribution in distributions_using_malone: |
|
217 |
yield self.getTerm(distribution) |
|
218 |
||
219 |
def __len__(self): |
|
220 |
return Distribution.selectBy(official_malone=True).count() |
|
221 |
||
222 |
def __contains__(self, obj): |
|
223 |
return (IDistribution.providedBy(obj) |
|
224 |
and obj.bug_tracking_usage == ServiceUsage.LAUNCHPAD) |
|
225 |
||
226 |
def getTerm(self, obj): |
|
227 |
if obj not in self: |
|
228 |
raise LookupError(obj) |
|
229 |
return SimpleTerm(obj, obj.name, obj.displayname) |
|
230 |
||
231 |
def getTermByToken(self, token): |
|
232 |
found_dist = Distribution.selectOneBy( |
|
233 |
name=token, official_malone=True) |
|
234 |
if found_dist is None: |
|
235 |
raise LookupError(token) |
|
236 |
return self.getTerm(found_dist) |
|
237 |
||
238 |
||
239 |
def BugNominatableSeriesVocabulary(context=None): |
|
240 |
"""Return a nominatable series vocabulary."""
|
|
241 |
if getUtility(ILaunchBag).distribution: |
|
242 |
return BugNominatableDistroSeriesVocabulary( |
|
243 |
context, getUtility(ILaunchBag).distribution) |
|
244 |
else: |
|
245 |
assert getUtility(ILaunchBag).product |
|
246 |
return BugNominatableProductSeriesVocabulary( |
|
247 |
context, getUtility(ILaunchBag).product) |
|
248 |
||
249 |
||
250 |
class BugNominatableSeriesVocabularyBase(NamedSQLObjectVocabulary): |
|
251 |
"""Base vocabulary class for series for which a bug can be nominated."""
|
|
252 |
||
253 |
def __iter__(self): |
|
254 |
bug = self.context.bug |
|
255 |
||
256 |
all_series = self._getNominatableObjects() |
|
257 |
||
258 |
for series in sorted(all_series, key=attrgetter("displayname")): |
|
259 |
if bug.canBeNominatedFor(series): |
|
260 |
yield self.toTerm(series) |
|
261 |
||
262 |
def toTerm(self, obj): |
|
263 |
return SimpleTerm(obj, obj.name, obj.name.capitalize()) |
|
264 |
||
265 |
def getTermByToken(self, token): |
|
266 |
obj = self._queryNominatableObjectByName(token) |
|
267 |
if obj is None: |
|
268 |
raise LookupError(token) |
|
269 |
||
270 |
return self.toTerm(obj) |
|
271 |
||
272 |
def _getNominatableObjects(self): |
|
273 |
"""Return the series objects that the bug can be nominated for."""
|
|
274 |
raise NotImplementedError |
|
275 |
||
276 |
def _queryNominatableObjectByName(self, name): |
|
277 |
"""Return the series object with the given name."""
|
|
278 |
raise NotImplementedError |
|
279 |
||
280 |
||
281 |
class BugNominatableProductSeriesVocabulary( |
|
282 |
BugNominatableSeriesVocabularyBase): |
|
283 |
"""The product series for which a bug can be nominated."""
|
|
284 |
||
285 |
_table = ProductSeries |
|
286 |
||
287 |
def __init__(self, context, product): |
|
288 |
BugNominatableSeriesVocabularyBase.__init__(self, context) |
|
289 |
self.product = product |
|
290 |
||
291 |
def _getNominatableObjects(self): |
|
292 |
"""See BugNominatableSeriesVocabularyBase."""
|
|
293 |
return shortlist(self.product.series) |
|
294 |
||
295 |
def _queryNominatableObjectByName(self, name): |
|
296 |
"""See BugNominatableSeriesVocabularyBase."""
|
|
297 |
return self.product.getSeries(name) |
|
298 |
||
299 |
||
300 |
class BugNominatableDistroSeriesVocabulary( |
|
301 |
BugNominatableSeriesVocabularyBase): |
|
302 |
"""The distribution series for which a bug can be nominated."""
|
|
303 |
||
304 |
_table = DistroSeries |
|
305 |
||
306 |
def __init__(self, context, distribution): |
|
307 |
BugNominatableSeriesVocabularyBase.__init__(self, context) |
|
308 |
self.distribution = distribution |
|
309 |
||
310 |
def _getNominatableObjects(self): |
|
311 |
"""Return all non-obsolete distribution series"""
|
|
312 |
return [ |
|
313 |
series for series in shortlist(self.distribution.series) |
|
314 |
if series.status != SeriesStatus.OBSOLETE] |
|
315 |
||
316 |
def _queryNominatableObjectByName(self, name): |
|
317 |
"""See BugNominatableSeriesVocabularyBase."""
|
|
318 |
return self.distribution.getSeries(name) |