~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/bugs/vocabularies.py

  • Committer: Francis J. Lacoste
  • Date: 2011-12-06 20:10:56 UTC
  • mfrom: (14448 devel)
  • mto: This revision was merged to the branch mainline in revision 14474.
  • Revision ID: francis.lacoste@canonical.com-20111206201056-jk8q5euywtd4vqk7
Merge devel.

Show diffs side-by-side

added added

removed removed

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