~launchpad-pqm/launchpad/devel

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
Vocabularies
============

Introduction
------------

Vocabularies are lists of terms. In Launchpad's Component Architecture
(CA), a vocabulary is a list of terms that a widget (normally a selection
style widget) "speaks", i.e., its allowed values.

    >>> from zope.component import getUtility
    >>> from canonical.launchpad.ftests import login
    >>> from canonical.database.sqlbase import flush_database_updates
    >>> from canonical.launchpad.webapp.interfaces import IOpenLaunchBag
    >>> from lp.registry.interfaces.person import IPersonSet
    >>> from lp.registry.interfaces.product import IProductSet
    >>> person_set = getUtility(IPersonSet)
    >>> product_set = getUtility(IProductSet)
    >>> login('foo.bar@canonical.com')
    >>> launchbag = getUtility(IOpenLaunchBag)
    >>> launchbag.clear()


Values, Tokens, and Titles
..........................

In Launchpad, we generally use "tokenized vocabularies." Each term in
a vocabulary has a value, token and title. A term is rendered in a
select widget like this:

<option value="$token">$title</option>

The $token is probably the data you would store in your DB. The Token is
used to uniquely identify a Term, and the Title is the thing you display
to the user.


Launchpad Vocabularies
----------------------

There are two kinds of vocabularies in Launchpad: enumerable and
non-enumerable. Enumerable vocabularies are short enough to render in a
select widget. Non-enumerable vocabularies require a query interface to make
it easy to choose just one or a couple of options from several hundred,
several thousand, or more.

Vocabularies should not be imported - they can be retrieved from the
vocabulary registry.

    >>> from zope.schema.vocabulary import getVocabularyRegistry
    >>> from zope.security.proxy import removeSecurityProxy
    >>> vocabulary_registry = getVocabularyRegistry()
    >>> def get_naked_vocab(context, name):
    ...     return removeSecurityProxy(
    ...         vocabulary_registry.get(context, name))
    >>> product_vocabulary = vocabulary_registry.get(None, "Product")
    >>> product_vocabulary.displayname
    'Select a project'


Iterating over non-enumerable vocabularies, while possible, will
probably kill the database. Instead, these vocabularies are
search-driven.


BinaryAndSourcePackageNameVocabulary
....................................

The list of binary and source package names, ordered by name.

    >>> package_name_vocabulary = vocabulary_registry.get(
    ...     None, "BinaryAndSourcePackageName")
    >>> package_name_vocabulary.displayname
    'Select a Package'

When a package name matches both a binary package name and a source
package of the exact same name, the binary package name is
returned. This allows us, in bug reporting for example, to collect the
most specific information possible.

Let's demonstrate by searching for "mozilla-firefox", for which there is
both a source and binary package of that name.

    >>> package_name_terms = package_name_vocabulary.searchForTerms(
    ...     "mozilla-firefox")
    >>> package_name_terms.count()
    2
    >>> [(term.token, term.title) for term in package_name_terms]
    [('mozilla-firefox', u'mozilla-firefox'),
     ('mozilla-firefox-data', u'mozilla-firefox-data')]

Searching for "mozilla" should return the binary package name above, and
the source package named "mozilla".

    >>> package_name_terms = package_name_vocabulary.searchForTerms("mozilla")
    >>> package_name_terms.count()
    3
    >>> [(term.token, term.title) for term in package_name_terms]
    [('mozilla', u'mozilla'),
     ('mozilla-firefox', u'mozilla-firefox'),
     ('mozilla-firefox-data', u'mozilla-firefox-data')]

The search does a case-insensitive, substring match.

    >>> package_name_terms = package_name_vocabulary.searchForTerms("lInuX")
    >>> package_name_terms.count()
    2
    >>> [(term.token, term.title) for term in package_name_terms]
    [('linux-2.6.12', u'linux-2.6.12'),
     ('linux-source-2.6.15', u'linux-source-2.6.15')]


BinaryPackageNameVocabulary
...........................

All the binary packages in Launchpad.

    >>> bpn_vocabulary = vocabulary_registry.get(None, 'BinaryPackageName')
    >>> len(bpn_vocabulary)
    8

    >>> bpn_terms = bpn_vocabulary.searchForTerms("mozilla")
    >>> len(bpn_terms)
    2
    >>> [(term.token, term.title) for term in bpn_terms]
    [('mozilla-firefox', u'iceweasel huh ?'),
     ('mozilla-firefox-data', u'Mozilla Firefox Data is .....')]


SourcePackageNameVocabulary
...........................

All the source packages in Launchpad.

    >>> spn_vocabulary = vocabulary_registry.get(None, 'SourcePackageName')
    >>> len(spn_vocabulary)
    17

    >>> spn_terms = spn_vocabulary.searchForTerms("mozilla")
    >>> len(spn_terms)
    2
    >>> [(term.token, term.title) for term in spn_terms]
    [('mozilla', u'mozilla'), ('mozilla-firefox', u'mozilla-firefox')]

    >>> spn_terms = spn_vocabulary.searchForTerms("pmount")
    >>> len(spn_terms)
    1
    >>> [(term.token, term.title) for term in spn_terms]
    [('pmount', u'pmount')]


Processor
.........

All processors type available in Launchpad.

    >>> vocab = vocabulary_registry.get(None, "Processor")
    >>> vocab.displayname
    'Select a processor'

    >>> [term.token for term in vocab.searchForTerms('386')]
    ['386']


PPA
...

The PPA vocabulary contains all the PPAs available in a particular
collection. It provides the IHugeVocabulary interface.

    >>> from canonical.launchpad.webapp.testing import verifyObject
    >>> from canonical.launchpad.webapp.vocabulary import IHugeVocabulary

    >>> vocabulary = get_naked_vocab(None, 'PPA')
    >>> verifyObject(IHugeVocabulary, vocabulary)
    True

    >>> print vocabulary.displayname
    Select a PPA

Iterations over the PPA vocabulary will return on PPA archives.

    >>> sorted([term.value.owner.name for term in vocabulary])
    [u'cprov', u'mark', u'no-priv']

PPA vocabulary terms contain:

 * token: the PPA owner name combined with the archive name (using '/');
 * value: the IArchive object;
 * title: the first line of the PPA description text.

    >>> cprov_term = vocabulary.getTermByToken('cprov/ppa')

    >>> print cprov_term.token
    cprov/ppa

    >>> print cprov_term.value
    <Archive ...>

    >>> print cprov_term.title
    packages to help my friends.

Not found terms result in LookupError.

    >>> vocabulary.getTermByToken('foobar')
    Traceback (most recent call last):
    ...
    LookupError: foobar

PPA vocabulary searches consider the owner FTI and the PPA FTI.

    >>> def print_search_results(results):
    ...     for archive in results:
    ...         term = vocabulary.toTerm(archive)
    ...         print '%s: %s' % (term.token, term.title)

    >>> cprov_search = vocabulary.search('cprov')
    >>> print_search_results(cprov_search)
    cprov/ppa: packages to help my friends.

    >>> celso_search = vocabulary.search('celso')
    >>> print_search_results(celso_search)
    cprov/ppa: packages to help my friends.

    >>> friends_search = vocabulary.search('friends')
    >>> print_search_results(friends_search)
    cprov/ppa: packages to help my friends.

We will create an additional PPA for Celso named 'testing'

    >>> from lp.soyuz.enums import ArchivePurpose
    >>> from lp.soyuz.interfaces.archive import IArchiveSet

    >>> login('foo.bar@canonical.com')
    >>> cprov = getUtility(IPersonSet).getByName('cprov')
    >>> cprov_testing = getUtility(IArchiveSet).new(
    ...     owner=cprov, name='testing', purpose=ArchivePurpose.PPA,
    ...     description='testing packages.')

Now, a search for 'cprov' will return 2 ppas and the result is ordered
by PPA name.

    >>> cprov_search = vocabulary.search('cprov')
    >>> print_search_results(cprov_search)
    cprov/ppa: packages to help my friends.
    cprov/testing: testing packages.

The vocabulary search also supports specific named PPA lookups
follwing the same combined syntax used to build unique tokens.

    >>> named_search = vocabulary.search('cprov/testing')
    >>> print_search_results(named_search)
    cprov/testing: testing packages.

As mentioned the PPA vocabulary term title only contains the first
line of the PPA description.

    >>> cprov.archive.description = "Single line."
    >>> flush_database_updates()

    >>> cprov_term = vocabulary.getTermByToken('cprov/ppa')
    >>> print cprov_term.title
    Single line.

    >>> cprov.archive.description = "First line\nSecond line."
    >>> flush_database_updates()

    >>> cprov_term = vocabulary.getTermByToken('cprov/ppa')
    >>> print cprov_term.title
    First line

PPAs with empty description are identified and have a title saying so.

    >>> cprov.archive.description = None
    >>> flush_database_updates()

    >>> cprov_term = vocabulary.getTermByToken('cprov/ppa')
    >>> print cprov_term.title
    No description available

Queries on empty strings also results in a valid SelectResults.

    >>> empty_search = vocabulary.search('')
    >>> empty_search.count() == 0
    True