~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/canonical/launchpad/vocabularies/dbobjects.py

  • Committer: Curtis Hovey
  • Date: 2011-08-21 14:21:06 UTC
  • mto: This revision was merged to the branch mainline in revision 13745.
  • Revision ID: curtis.hovey@canonical.com-20110821142106-x93hajd6iguma8gx
Update test that was enforcing bad grammar.

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# Copyright 2009-2011 Canonical Ltd.  This software is licensed under the GNU
 
1
# Copyright 2009, 2010 Canonical Ltd.  This software is licensed under the GNU
2
2
# Affero General Public License version 3 (see the file LICENSE).
3
3
 
4
 
"""Translations vocabularies."""
 
4
"""Vocabularies pulling stuff from the database.
 
5
 
 
6
You probably don't want to use these classes directly - see the
 
7
docstring in __init__.py for details.
 
8
"""
5
9
 
6
10
__metaclass__ = type
7
11
 
8
12
__all__ = [
 
13
    'BugNominatableDistroSeriesVocabulary',
 
14
    'BugNominatableProductSeriesVocabulary',
 
15
    'BugNominatableSeriesVocabulary',
 
16
    'BugTrackerVocabulary',
 
17
    'BugVocabulary',
 
18
    'BugWatchVocabulary',
 
19
    'ComponentVocabulary',
 
20
    'CountryNameVocabulary',
 
21
    'DistributionUsingMaloneVocabulary',
9
22
    'FilteredDeltaLanguagePackVocabulary',
 
23
    'FilteredDistroArchSeriesVocabulary',
10
24
    'FilteredFullLanguagePackVocabulary',
11
25
    'FilteredLanguagePackVocabulary',
 
26
    'LanguageVocabulary',
 
27
    'PackageReleaseVocabulary',
 
28
    'PPAVocabulary',
 
29
    'ProcessorFamilyVocabulary',
 
30
    'ProcessorVocabulary',
 
31
    'project_products_using_malone_vocabulary_factory',
12
32
    'TranslatableLanguageVocabulary',
13
33
    'TranslationGroupVocabulary',
14
34
    'TranslationMessageVocabulary',
15
35
    'TranslationTemplateVocabulary',
 
36
    'WebBugTrackerVocabulary',
16
37
    ]
17
38
 
18
 
from sqlobject import AND
19
 
from zope.schema.vocabulary import SimpleTerm
20
 
 
21
 
from canonical.database.sqlbase import sqlvalues
 
39
import cgi
 
40
from operator import attrgetter
 
41
 
 
42
from sqlobject import (
 
43
    AND,
 
44
    CONTAINSSTRING,
 
45
    SQLObjectNotFound,
 
46
    )
 
47
from storm.expr import (
 
48
    And,
 
49
    Or,
 
50
    SQL,
 
51
    )
 
52
from zope.component import getUtility
 
53
from zope.interface import implements
 
54
from zope.schema.interfaces import (
 
55
    IVocabulary,
 
56
    IVocabularyTokenized,
 
57
    )
 
58
from zope.schema.vocabulary import (
 
59
    SimpleTerm,
 
60
    SimpleVocabulary,
 
61
    )
 
62
 
 
63
from canonical.database.sqlbase import (
 
64
    quote,
 
65
    sqlvalues,
 
66
    )
 
67
from canonical.launchpad.helpers import (
 
68
    ensure_unicode,
 
69
    shortlist,
 
70
    )
 
71
from canonical.launchpad.interfaces.lpstorm import IStore
 
72
from canonical.launchpad.webapp.interfaces import ILaunchBag
22
73
from canonical.launchpad.webapp.vocabulary import (
 
74
    CountableIterator,
 
75
    IHugeVocabulary,
23
76
    NamedSQLObjectVocabulary,
24
77
    SQLObjectVocabularyBase,
25
78
    )
 
79
from lp.app.browser.stringformatter import FormattersAPI
 
80
from lp.app.enums import ServiceUsage
 
81
from lp.bugs.interfaces.bugtask import IBugTask
 
82
from lp.bugs.interfaces.bugtracker import BugTrackerType
 
83
from lp.bugs.model.bug import Bug
 
84
from lp.bugs.model.bugtracker import BugTracker
 
85
from lp.bugs.model.bugwatch import BugWatch
 
86
from lp.registry.interfaces.distribution import IDistribution
26
87
from lp.registry.interfaces.distroseries import IDistroSeries
 
88
from lp.registry.interfaces.projectgroup import IProjectGroup
 
89
from lp.registry.interfaces.series import SeriesStatus
 
90
from lp.registry.model.distribution import Distribution
 
91
from lp.registry.model.distroseries import DistroSeries
 
92
from lp.registry.model.person import Person
 
93
from lp.registry.model.productseries import ProductSeries
27
94
from lp.services.worlddata.interfaces.language import ILanguage
28
 
from lp.services.worlddata.vocabularies import LanguageVocabulary
 
95
from lp.services.worlddata.model.country import Country
 
96
from lp.services.worlddata.model.language import Language
 
97
from lp.soyuz.enums import ArchivePurpose
 
98
from lp.soyuz.model.archive import Archive
 
99
from lp.soyuz.model.component import Component
 
100
from lp.soyuz.model.distroarchseries import DistroArchSeries
 
101
from lp.soyuz.model.processor import (
 
102
    Processor,
 
103
    ProcessorFamily,
 
104
    )
 
105
from lp.soyuz.model.sourcepackagerelease import SourcePackageRelease
29
106
from lp.translations.enums import LanguagePackType
30
107
from lp.translations.model.languagepack import LanguagePack
31
108
from lp.translations.model.potemplate import POTemplate
33
110
from lp.translations.model.translationmessage import TranslationMessage
34
111
 
35
112
 
 
113
class ComponentVocabulary(SQLObjectVocabularyBase):
 
114
 
 
115
    _table = Component
 
116
    _orderBy = 'name'
 
117
 
 
118
    def toTerm(self, obj):
 
119
        return SimpleTerm(obj, obj.id, obj.name)
 
120
 
 
121
 
 
122
# Country.name may have non-ASCII characters, so we can't use
 
123
# NamedSQLObjectVocabulary here.
 
124
 
 
125
class CountryNameVocabulary(SQLObjectVocabularyBase):
 
126
    """A vocabulary for country names."""
 
127
 
 
128
    _table = Country
 
129
    _orderBy = 'name'
 
130
 
 
131
    def toTerm(self, obj):
 
132
        return SimpleTerm(obj, obj.id, obj.name)
 
133
 
 
134
 
 
135
class BugVocabulary(SQLObjectVocabularyBase):
 
136
 
 
137
    _table = Bug
 
138
    _orderBy = 'id'
 
139
 
 
140
 
 
141
class BugTrackerVocabulary(SQLObjectVocabularyBase):
 
142
    """All web and email based external bug trackers."""
 
143
    displayname = 'Select a bug tracker'
 
144
    step_title = 'Search'
 
145
    implements(IHugeVocabulary)
 
146
    _table = BugTracker
 
147
    _filter = True
 
148
    _orderBy = 'title'
 
149
    _order_by = [BugTracker.title]
 
150
 
 
151
    def toTerm(self, obj):
 
152
        """See `IVocabulary`."""
 
153
        return SimpleTerm(obj, obj.name, obj.title)
 
154
 
 
155
    def getTermByToken(self, token):
 
156
        """See `IVocabularyTokenized`."""
 
157
        result = IStore(self._table).find(
 
158
            self._table,
 
159
            self._filter,
 
160
            BugTracker.name == token).one()
 
161
        if result is None:
 
162
            raise LookupError(token)
 
163
        return self.toTerm(result)
 
164
 
 
165
    def search(self, query):
 
166
        """Search for web bug trackers."""
 
167
        query = ensure_unicode(query).lower()
 
168
        results = IStore(self._table).find(
 
169
            self._table, And(
 
170
            self._filter,
 
171
            BugTracker.active == True,
 
172
            Or(
 
173
                CONTAINSSTRING(BugTracker.name, query),
 
174
                CONTAINSSTRING(BugTracker.title, query),
 
175
                CONTAINSSTRING(BugTracker.summary, query),
 
176
                CONTAINSSTRING(BugTracker.baseurl, query))))
 
177
        results = results.order_by(self._order_by)
 
178
        return results
 
179
 
 
180
    def searchForTerms(self, query=None):
 
181
        """See `IHugeVocabulary`."""
 
182
        results = self.search(query)
 
183
        return CountableIterator(results.count(), results, self.toTerm)
 
184
 
 
185
 
 
186
class WebBugTrackerVocabulary(BugTrackerVocabulary):
 
187
    """All web-based bug tracker types."""
 
188
    _filter = BugTracker.bugtrackertype != BugTrackerType.EMAILADDRESS
 
189
 
 
190
 
 
191
class LanguageVocabulary(SQLObjectVocabularyBase):
 
192
    """All the languages known by Launchpad."""
 
193
 
 
194
    _table = Language
 
195
    _orderBy = 'englishname'
 
196
 
 
197
    def __contains__(self, language):
 
198
        """See `IVocabulary`."""
 
199
        assert ILanguage.providedBy(language), (
 
200
            "'in LanguageVocabulary' requires ILanguage as left operand, "
 
201
            "got %s instead." % type(language))
 
202
        return super(LanguageVocabulary, self).__contains__(language)
 
203
 
 
204
    def toTerm(self, obj):
 
205
        """See `IVocabulary`."""
 
206
        return SimpleTerm(obj, obj.code, obj.displayname)
 
207
 
 
208
    def getTerm(self, obj):
 
209
        """See `IVocabulary`."""
 
210
        if obj not in self:
 
211
            raise LookupError(obj)
 
212
        return SimpleTerm(obj, obj.code, obj.displayname)
 
213
 
 
214
    def getTermByToken(self, token):
 
215
        """See `IVocabulary`."""
 
216
        try:
 
217
            found_language = Language.byCode(token)
 
218
        except SQLObjectNotFound:
 
219
            raise LookupError(token)
 
220
        return self.getTerm(found_language)
 
221
 
 
222
 
36
223
class TranslatableLanguageVocabulary(LanguageVocabulary):
37
224
    """All the translatable languages known by Launchpad.
38
225
 
76
263
        return term
77
264
 
78
265
 
 
266
def project_products_using_malone_vocabulary_factory(context):
 
267
    """Return a vocabulary containing a project's products using Malone."""
 
268
    project = IProjectGroup(context)
 
269
    return SimpleVocabulary([
 
270
        SimpleTerm(product, product.name, title=product.displayname)
 
271
        for product in project.products
 
272
        if product.bug_tracking_usage == ServiceUsage.LAUNCHPAD])
 
273
 
 
274
 
79
275
class TranslationGroupVocabulary(NamedSQLObjectVocabulary):
80
276
 
81
277
    _table = TranslationGroup
119
315
        return SimpleTerm(obj, obj.id, obj.name)
120
316
 
121
317
 
 
318
class FilteredDistroArchSeriesVocabulary(SQLObjectVocabularyBase):
 
319
    """All arch series of a particular distribution."""
 
320
 
 
321
    _table = DistroArchSeries
 
322
    _orderBy = ['DistroSeries.version', 'architecturetag', 'id']
 
323
    _clauseTables = ['DistroSeries']
 
324
 
 
325
    def toTerm(self, obj):
 
326
        name = "%s %s (%s)" % (obj.distroseries.distribution.name,
 
327
                               obj.distroseries.name, obj.architecturetag)
 
328
        return SimpleTerm(obj, obj.id, name)
 
329
 
 
330
    def __iter__(self):
 
331
        distribution = getUtility(ILaunchBag).distribution
 
332
        if distribution:
 
333
            query = """
 
334
                DistroSeries.id = DistroArchSeries.distroseries AND
 
335
                DistroSeries.distribution = %s
 
336
                """ % sqlvalues(distribution.id)
 
337
            results = self._table.select(
 
338
                query, orderBy=self._orderBy, clauseTables=self._clauseTables)
 
339
            for distroarchseries in results:
 
340
                yield self.toTerm(distroarchseries)
 
341
 
 
342
 
 
343
class BugWatchVocabulary(SQLObjectVocabularyBase):
 
344
    _table = BugWatch
 
345
 
 
346
    def __iter__(self):
 
347
        assert IBugTask.providedBy(self.context), (
 
348
            "BugWatchVocabulary expects its context to be an IBugTask.")
 
349
        bug = self.context.bug
 
350
 
 
351
        for watch in bug.watches:
 
352
            yield self.toTerm(watch)
 
353
 
 
354
    def toTerm(self, watch):
 
355
 
 
356
        def escape(string):
 
357
            return cgi.escape(string, quote=True)
 
358
 
 
359
        if watch.url.startswith('mailto:'):
 
360
            user = getUtility(ILaunchBag).user
 
361
            if user is None:
 
362
                title = FormattersAPI(
 
363
                    watch.bugtracker.title).obfuscate_email()
 
364
                return SimpleTerm(
 
365
                    watch, watch.id, escape(title))
 
366
            else:
 
367
                url = watch.url
 
368
                title = escape(watch.bugtracker.title)
 
369
                if url in title:
 
370
                    title = title.replace(
 
371
                        url, '<a href="%s">%s</a>' % (
 
372
                            escape(url), escape(url)))
 
373
                else:
 
374
                    title = '%s &lt;<a href="%s">%s</a>&gt;' % (
 
375
                        title, escape(url), escape(url[7:]))
 
376
                return SimpleTerm(watch, watch.id, title)
 
377
        else:
 
378
            return SimpleTerm(
 
379
                watch, watch.id, '%s <a href="%s">#%s</a>' % (
 
380
                    escape(watch.bugtracker.title),
 
381
                    escape(watch.url),
 
382
                    escape(watch.remotebug)))
 
383
 
 
384
 
 
385
class PackageReleaseVocabulary(SQLObjectVocabularyBase):
 
386
    _table = SourcePackageRelease
 
387
    _orderBy = 'id'
 
388
 
 
389
    def toTerm(self, obj):
 
390
        return SimpleTerm(
 
391
            obj, obj.id, obj.name + " " + obj.version)
 
392
 
 
393
 
 
394
class PPAVocabulary(SQLObjectVocabularyBase):
 
395
 
 
396
    implements(IHugeVocabulary)
 
397
 
 
398
    _table = Archive
 
399
    _orderBy = ['Person.name, Archive.name']
 
400
    _clauseTables = ['Person']
 
401
    _filter = AND(
 
402
        Person.q.id == Archive.q.ownerID,
 
403
        Archive.q.purpose == ArchivePurpose.PPA)
 
404
    displayname = 'Select a PPA'
 
405
    step_title = 'Search'
 
406
 
 
407
    def toTerm(self, archive):
 
408
        """See `IVocabulary`."""
 
409
        description = archive.description
 
410
        if description is not None:
 
411
            summary = description.splitlines()[0]
 
412
        else:
 
413
            summary = "No description available"
 
414
 
 
415
        token = '%s/%s' % (archive.owner.name, archive.name)
 
416
 
 
417
        return SimpleTerm(archive, token, summary)
 
418
 
 
419
    def getTermByToken(self, token):
 
420
        """See `IVocabularyTokenized`."""
 
421
        try:
 
422
            owner_name, archive_name = token.split('/')
 
423
        except ValueError:
 
424
            raise LookupError(token)
 
425
 
 
426
        clause = AND(
 
427
            self._filter,
 
428
            Person.name == owner_name,
 
429
            Archive.name == archive_name)
 
430
 
 
431
        obj = self._table.selectOne(
 
432
            clause, clauseTables=self._clauseTables)
 
433
 
 
434
        if obj is None:
 
435
            raise LookupError(token)
 
436
        else:
 
437
            return self.toTerm(obj)
 
438
 
 
439
    def search(self, query):
 
440
        """Return a resultset of archives.
 
441
 
 
442
        This is a helper required by `SQLObjectVocabularyBase.searchForTerms`.
 
443
        """
 
444
        if not query:
 
445
            return self.emptySelectResults()
 
446
 
 
447
        query = query.lower()
 
448
 
 
449
        try:
 
450
            owner_name, archive_name = query.split('/')
 
451
        except ValueError:
 
452
            clause = AND(
 
453
                self._filter,
 
454
                SQL("(Archive.fti @@ ftq(%s) OR Person.fti @@ ftq(%s))"
 
455
                    % (quote(query), quote(query))))
 
456
        else:
 
457
            clause = AND(
 
458
                self._filter,
 
459
                Person.name == owner_name,
 
460
                Archive.name == archive_name)
 
461
 
 
462
        return self._table.select(
 
463
            clause, orderBy=self._orderBy, clauseTables=self._clauseTables)
 
464
 
 
465
 
 
466
class DistributionUsingMaloneVocabulary:
 
467
    """All the distributions that uses Malone officially."""
 
468
 
 
469
    implements(IVocabulary, IVocabularyTokenized)
 
470
 
 
471
    _orderBy = 'displayname'
 
472
 
 
473
    def __init__(self, context=None):
 
474
        self.context = context
 
475
 
 
476
    def __iter__(self):
 
477
        """Return an iterator which provides the terms from the vocabulary."""
 
478
        distributions_using_malone = Distribution.selectBy(
 
479
            official_malone=True, orderBy=self._orderBy)
 
480
        for distribution in distributions_using_malone:
 
481
            yield self.getTerm(distribution)
 
482
 
 
483
    def __len__(self):
 
484
        return Distribution.selectBy(official_malone=True).count()
 
485
 
 
486
    def __contains__(self, obj):
 
487
        return (IDistribution.providedBy(obj)
 
488
                and obj.bug_tracking_usage == ServiceUsage.LAUNCHPAD)
 
489
 
 
490
    def getTerm(self, obj):
 
491
        if obj not in self:
 
492
            raise LookupError(obj)
 
493
        return SimpleTerm(obj, obj.name, obj.displayname)
 
494
 
 
495
    def getTermByToken(self, token):
 
496
        found_dist = Distribution.selectOneBy(
 
497
            name=token, official_malone=True)
 
498
        if found_dist is None:
 
499
            raise LookupError(token)
 
500
        return self.getTerm(found_dist)
 
501
 
 
502
 
 
503
class ProcessorVocabulary(NamedSQLObjectVocabulary):
 
504
 
 
505
    displayname = 'Select a processor'
 
506
    _table = Processor
 
507
    _orderBy = 'name'
 
508
 
 
509
 
 
510
class ProcessorFamilyVocabulary(NamedSQLObjectVocabulary):
 
511
    displayname = 'Select a processor family'
 
512
    _table = ProcessorFamily
 
513
    _orderBy = 'name'
 
514
 
 
515
 
 
516
def BugNominatableSeriesVocabulary(context=None):
 
517
    """Return a nominatable series vocabulary."""
 
518
    if getUtility(ILaunchBag).distribution:
 
519
        return BugNominatableDistroSeriesVocabulary(
 
520
            context, getUtility(ILaunchBag).distribution)
 
521
    else:
 
522
        assert getUtility(ILaunchBag).product
 
523
        return BugNominatableProductSeriesVocabulary(
 
524
            context, getUtility(ILaunchBag).product)
 
525
 
 
526
 
 
527
class BugNominatableSeriesVocabularyBase(NamedSQLObjectVocabulary):
 
528
    """Base vocabulary class for series for which a bug can be nominated."""
 
529
 
 
530
    def __iter__(self):
 
531
        bug = self.context.bug
 
532
 
 
533
        all_series = self._getNominatableObjects()
 
534
 
 
535
        for series in sorted(all_series, key=attrgetter("displayname")):
 
536
            if bug.canBeNominatedFor(series):
 
537
                yield self.toTerm(series)
 
538
 
 
539
    def toTerm(self, obj):
 
540
        return SimpleTerm(obj, obj.name, obj.name.capitalize())
 
541
 
 
542
    def getTermByToken(self, token):
 
543
        obj = self._queryNominatableObjectByName(token)
 
544
        if obj is None:
 
545
            raise LookupError(token)
 
546
 
 
547
        return self.toTerm(obj)
 
548
 
 
549
    def _getNominatableObjects(self):
 
550
        """Return the series objects that the bug can be nominated for."""
 
551
        raise NotImplementedError
 
552
 
 
553
    def _queryNominatableObjectByName(self, name):
 
554
        """Return the series object with the given name."""
 
555
        raise NotImplementedError
 
556
 
 
557
 
 
558
class BugNominatableProductSeriesVocabulary(
 
559
    BugNominatableSeriesVocabularyBase):
 
560
    """The product series for which a bug can be nominated."""
 
561
 
 
562
    _table = ProductSeries
 
563
 
 
564
    def __init__(self, context, product):
 
565
        BugNominatableSeriesVocabularyBase.__init__(self, context)
 
566
        self.product = product
 
567
 
 
568
    def _getNominatableObjects(self):
 
569
        """See BugNominatableSeriesVocabularyBase."""
 
570
        return shortlist(self.product.series)
 
571
 
 
572
    def _queryNominatableObjectByName(self, name):
 
573
        """See BugNominatableSeriesVocabularyBase."""
 
574
        return self.product.getSeries(name)
 
575
 
 
576
 
 
577
class BugNominatableDistroSeriesVocabulary(
 
578
    BugNominatableSeriesVocabularyBase):
 
579
    """The distribution series for which a bug can be nominated."""
 
580
 
 
581
    _table = DistroSeries
 
582
 
 
583
    def __init__(self, context, distribution):
 
584
        BugNominatableSeriesVocabularyBase.__init__(self, context)
 
585
        self.distribution = distribution
 
586
 
 
587
    def _getNominatableObjects(self):
 
588
        """Return all non-obsolete distribution series"""
 
589
        return [
 
590
            series for series in shortlist(self.distribution.series)
 
591
            if series.status != SeriesStatus.OBSOLETE]
 
592
 
 
593
    def _queryNominatableObjectByName(self, name):
 
594
        """See BugNominatableSeriesVocabularyBase."""
 
595
        return self.distribution.getSeries(name)
 
596
 
 
597
 
122
598
class FilteredLanguagePackVocabularyBase(SQLObjectVocabularyBase):
123
599
    """Base vocabulary class to retrieve language packs for a distroseries."""
124
600
    _table = LanguagePack