~launchpad-pqm/launchpad/devel

« back to all changes in this revision

Viewing changes to lib/lp/answers/doc/question.txt

Merged db-devel

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
= Launchpad Answer Tracker =
 
1
========================
 
2
Launchpad Answer Tracker
 
3
========================
2
4
 
3
 
Launchpad includes an Answer Tracker where users can post questions
4
 
(usually about problems they encounter with projects) and other can
5
 
answer them.) Questions are created and accessed using the
6
 
IQuestionTarget interface. This interface is available on Products,
7
 
Distributions and DistributionSourcePackages.
 
5
Launchpad includes an Answer Tracker where users can post questions, usually
 
6
about problems they encounter with projects, and other people can answer them.
 
7
Questions are created and accessed using the IQuestionTarget interface.  This
 
8
interface is available on Products, Distributions and
 
9
DistributionSourcePackages.
8
10
 
9
11
    >>> login('test@canonical.com')
10
12
 
11
13
    >>> from canonical.launchpad.webapp.testing import verifyObject
12
 
    >>> from canonical.launchpad.interfaces import (
13
 
    ...     IDistributionSet, IProductSet, IPersonSet, IQuestionTarget)
 
14
    >>> from lp.answers.interfaces.questiontarget import IQuestionTarget
 
15
    >>> from lp.registry.interfaces.product import IProductSet
14
16
 
15
17
    >>> firefox = getUtility(IProductSet)['firefox']
16
18
    >>> verifyObject(IQuestionTarget, firefox)
17
19
    True
18
20
 
 
21
    >>> from lp.registry.interfaces.distribution import IDistributionSet
19
22
    >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
20
23
    >>> verifyObject(IQuestionTarget, ubuntu)
21
24
    True
24
27
    >>> verifyObject(IQuestionTarget, evolution_in_ubuntu)
25
28
    True
26
29
 
27
 
Although Distribution series do not implement the IQuestionTarget
28
 
interface, it is possible to adapt one to it. (The adapter is actually
29
 
the distroseries's distribution.)
 
30
Although distribution series do not implement the IQuestionTarget interface,
 
31
it is possible to adapt one to it.  The adapter is actually the distroseries's
 
32
distribution.
30
33
 
31
34
    >>> ubuntu_warty = ubuntu.getSeries('warty')
32
35
    >>> IQuestionTarget.providedBy(ubuntu_warty)
56
59
You create a new question by calling the newQuestion() method of an
57
60
IQuestionTarget attribute.
58
61
 
 
62
    >>> from lp.registry.interfaces.person import IPersonSet
59
63
    >>> sample_person = getUtility(IPersonSet).getByEmail('test@canonical.com')
60
64
    >>> firefox_question = firefox.newQuestion(
61
65
    ...     sample_person, "Firefox question", "Unable to use Firefox")
62
66
 
63
 
(The complete IQuestionTarget interface is documented in
64
 
../interfaces/ftests/questiontarget.txt.)
65
 
 
66
 
== Official usage ==
67
 
 
68
 
A product or distribution may be offically supported by the community
69
 
using the Answer Tracker. This status is set by the official_answers
70
 
attribute on the IProduct and IDistribution.
 
67
The complete IQuestionTarget interface is documented in questiontarget.txt.
 
68
 
 
69
 
 
70
Official usage
 
71
==============
 
72
 
 
73
A product or distribution may be officially supported by the community using
 
74
the Answer Tracker.  This status is set by the official_answers attribute on
 
75
the IProduct and IDistribution.
71
76
 
72
77
    >>> ubuntu.official_answers
73
78
    True
74
79
    >>> firefox.official_answers
75
80
    True
76
81
 
77
 
== IQuestion ==
78
 
 
79
 
Questions are manipulated through the IQuestion interface:
80
 
 
81
 
    >>> from canonical.launchpad.interfaces import IQuestion
 
82
 
 
83
IQuestion interface
 
84
===================
 
85
 
 
86
Questions are manipulated through the IQuestion interface.
 
87
 
 
88
    >>> from lp.answers.interfaces.question import IQuestion
82
89
    >>> from zope.security.proxy import removeSecurityProxy
83
90
 
84
91
    # The complete interface is not necessarily available to the
88
95
 
89
96
The person who submitted the question is available in the owner field.
90
97
 
91
 
    >>> firefox_question.owner == sample_person
92
 
    True
 
98
    >>> firefox_question.owner
 
99
    <Person at ... name12 (Sample Person)>
93
100
 
94
101
When the question is created, the owner is added to the question's
95
 
subscribers:
96
 
 
97
 
    >>> sample_person in [s.person for s in firefox_question.subscriptions]
98
 
    True
99
 
 
100
 
The question status is 'Open':
101
 
 
102
 
    >>> firefox_question.status.title
103
 
    'Open'
104
 
 
105
 
And the creation time is recorded in the datecreated attribute:
 
102
subscribers.
 
103
 
 
104
    >>> from operator import attrgetter
 
105
    >>> def print_subscribers(question):
 
106
    ...     people = [subscription.person
 
107
    ...               for subscription in question.subscriptions]
 
108
    ...     for person in sorted(people, key=attrgetter('name')):
 
109
    ...         print person.displayname
 
110
    >>> print_subscribers(firefox_question)
 
111
    Sample Person
 
112
 
 
113
The question status is 'Open'.
 
114
 
 
115
    >>> print firefox_question.status.title
 
116
    Open
 
117
 
 
118
The question has a creation time.
106
119
 
107
120
    >>> from datetime import datetime, timedelta
108
121
    >>> from pytz import UTC
110
123
    >>> now - firefox_question.datecreated < timedelta(seconds=5)
111
124
    True
112
125
 
113
 
The target onto which the question was created is available through the
114
 
'target' attribute:
 
126
The target onto which the question was created is also available.
115
127
 
116
 
    >>> firefox_question.target == firefox
117
 
    True
 
128
    >>> print firefox_question.target.displayname
 
129
    Mozilla Firefox
118
130
 
119
131
It is also possible to adapt a question to its IQuestionTarget.
120
132
 
163
175
    firefox
164
176
 
165
177
 
166
 
== Subscriptions and Notifications ==
 
178
Subscriptions and notifications
 
179
===============================
167
180
 
168
181
Whenever a question is created or changed, email notifications will be
169
 
sent. To receive such notification, one can subscribe to the bug using
 
182
sent.  To receive such notification, one can subscribe to the bug using
170
183
the subscribe() method.
171
184
 
172
185
    >>> no_priv = getUtility(IPersonSet).getByName('no-priv')
173
186
    >>> subscription = firefox_question.subscribe(no_priv)
174
187
 
175
 
The list of subscriptions is available in the subscriptions attribute.
176
 
In the current case, the subscribers will include the owner
177
 
('Sample Person') and the newly subscribed person.
 
188
The subscribers include the owner and the newly subscribed person.
178
189
 
179
 
    >>> [s.person.displayname for s in firefox_question.subscriptions]
180
 
    [u'Sample Person', u'No Privileges Person']
 
190
    >>> print_subscribers(firefox_question)
 
191
    Sample Person
 
192
    No Privileges Person
181
193
 
182
194
The getDirectSubscribers() method returns a sorted list of subscribers.
183
195
This method iterates like the NotificationRecipientSet returned by the
184
196
getDirectRecipients() method.
185
197
 
186
 
    >>> [person.displayname
187
 
    ...  for person in firefox_question.getDirectSubscribers()]
188
 
    [u'No Privileges Person', u'Sample Person']
 
198
    >>> for person in firefox_question.getDirectSubscribers():
 
199
    ...     print person.displayname
 
200
    No Privileges Person
 
201
    Sample Person
189
202
 
190
203
To remove a person from the subscriptions list, we use the unsubscribe()
191
204
method.
192
205
 
193
206
    >>> firefox_question.unsubscribe(no_priv)
194
 
    >>> [s.person.displayname for s in firefox_question.subscriptions]
195
 
    [u'Sample Person']
 
207
    >>> print_subscribers(firefox_question)
 
208
    Sample Person
196
209
 
197
 
The persons who are on the subscription list are said to be directly
198
 
subscribed to the question. They explicitly choose to get notifications
199
 
about that particular question. This list of persons is available through
200
 
the getDirectRecipients() method.
 
210
The people on the subscription list are said to be directly subscribed to the
 
211
question.  They explicitly chose to get notifications about that particular
 
212
question.  This list of people is available through the getDirectRecipients()
 
213
method.
201
214
 
202
215
    >>> subscribers = firefox_question.getDirectRecipients()
203
216
 
204
217
That method returns an INotificationRecipientSet, containing the direct
205
 
subscribers along the rationale for contacting them:
 
218
subscribers along with the rationale for contacting them.
206
219
 
207
220
    >>> from canonical.launchpad.interfaces import INotificationRecipientSet
208
221
    >>> verifyObject(INotificationRecipientSet, subscribers)
209
222
    True
210
 
    >>> [person.displayname for person in subscribers]
211
 
    [u'Sample Person']
212
 
    >>> subscribers.getReason(sample_person)
213
 
    ('You received this question notification because you are a direct
214
 
      subscriber of the question.', 'Subscriber')
 
223
    >>> def print_reason(subscribers):
 
224
    ...     for person in subscribers:
 
225
    ...         text, header = subscribers.getReason(person)
 
226
    ...         print header, person.displayname, text
 
227
    >>> print_reason(subscribers)
 
228
    Subscriber Sample Person You received this question notification
 
229
    because you are a direct subscriber of the question.
215
230
 
216
 
There is also a list of 'indirect' subscribers to the question. These
217
 
are persons that didn't explicitly subscribed to the question, but that
218
 
will receive notifications for other reason. Answer contacts for the
219
 
question target are part of the indirect subscribers list.
 
231
There is also a list of 'indirect' subscribers to the question.  These are
 
232
people that didn't explicitly subscribe to the question, but that will receive
 
233
notifications for other reasons.  Answer contacts for the question target are
 
234
part of the indirect subscribers list.
220
235
 
221
236
    # There are no answer contacts on the firefox product.
222
 
    >>> [person.displayname
223
 
    ...     for person in firefox_question.getIndirectRecipients()]
 
237
    >>> list(firefox_question.getIndirectRecipients())
224
238
    []
225
 
    >>> from canonical.launchpad.interfaces import ILanguageSet
 
239
 
 
240
    >>> from lp.services.worlddata.interfaces.language import ILanguageSet
226
241
    >>> english = getUtility(ILanguageSet)['en']
227
242
    >>> no_priv.addLanguage(english)
228
243
    >>> firefox.addAnswerContact(no_priv)
231
246
    >>> indirect_subscribers = firefox_question.getIndirectRecipients()
232
247
    >>> verifyObject(INotificationRecipientSet, indirect_subscribers)
233
248
    True
234
 
    >>> [person.displayname for person in indirect_subscribers]
235
 
    [u'No Privileges Person']
236
 
    >>> indirect_subscribers.getReason(no_priv)
237
 
    (u'You received this question notification because you are an answer
238
 
      contact for Mozilla Firefox.',
239
 
     u'Answer Contact (Mozilla Firefox)')
 
249
    >>> print_reason(indirect_subscribers)
 
250
    Answer Contact (Mozilla Firefox) No Privileges Person
 
251
    You received this question notification because you are an answer
 
252
    contact for Mozilla Firefox.
240
253
 
241
 
There is a special case for when the question's is associated to a
242
 
source package. The answer contacts for both the distribution and the
243
 
source package are part of the indirect subscribers list.
 
254
There is a special case for when the question is associated with a source
 
255
package.  The answer contacts for both the distribution and the source package
 
256
are part of the indirect subscribers list.
244
257
 
245
258
    # Let's register some answer contacts for the distribution and
246
259
    # the package.
257
270
    >>> package_question = evolution_in_ubuntu.newQuestion(
258
271
    ...     sample_person, 'Upgrading to Evolution 1.4 breaks plug-ins',
259
272
    ...     "The FnordsHighlighter plug-in doesn't work after upgrade.")
260
 
    >>> [s.person.displayname for s in package_question.subscriptions]
261
 
    [u'Sample Person']
 
273
 
 
274
    >>> print_subscribers(package_question)
 
275
    Sample Person
 
276
 
262
277
    >>> indirect_subscribers = package_question.getIndirectRecipients()
263
 
    >>> [person.displayname for person in indirect_subscribers]
264
 
    [u'No Privileges Person', u'Ubuntu Team']
265
 
    >>> indirect_subscribers.getReason(ubuntu_team)
266
 
    (u'You received this question notification because you are a member of
267
 
      Ubuntu Team, which is an answer contact for Ubuntu.',
268
 
     u'Answer Contact (ubuntu) @ubuntu-team')
269
 
    >>> indirect_subscribers.getReason(no_priv)
270
 
    (u'You received this question notification because you are an answer
271
 
      contact for evolution in ubuntu.',
272
 
     u'Answer Contact (evolution in ubuntu)')
 
278
    >>> for person in indirect_subscribers:
 
279
    ...     print person.displayname
 
280
    No Privileges Person
 
281
    Ubuntu Team
 
282
 
 
283
    >>> text, header = indirect_subscribers.getReason(ubuntu_team)
 
284
    >>> print header, text
 
285
    Answer Contact (ubuntu) @ubuntu-team
 
286
    You received this question notification because you are a member of
 
287
    Ubuntu Team, which is an answer contact for Ubuntu.
273
288
 
274
289
The question's assignee is also part of the indirect subscription list:
275
290
 
276
 
    >>> login('foo.bar@canonical.com')
 
291
    >>> login('admin@canonical.com')
277
292
    >>> package_question.assignee = getUtility(IPersonSet).getByName('name16')
278
293
    >>> indirect_subscribers = package_question.getIndirectRecipients()
279
 
    >>> [person.displayname for person in indirect_subscribers]
280
 
    [u'Foo Bar', u'No Privileges Person', u'Ubuntu Team']
281
 
    >>> indirect_subscribers.getReason(package_question.assignee)
282
 
    ('You received this question notification because you are the assignee
283
 
      for this question.',
284
 
     'Assignee')
 
294
    >>> for person in indirect_subscribers:
 
295
    ...     print person.displayname
 
296
    Foo Bar
 
297
    No Privileges Person
 
298
    Ubuntu Team
 
299
 
 
300
    >>> text, header = indirect_subscribers.getReason(
 
301
    ...     package_question.assignee)
 
302
    >>> print header, text
 
303
    Assignee
 
304
    You received this question notification because you are the assignee for
 
305
    this question.
285
306
 
286
307
The getIndirectSubscribers() method iterates like the getIndirectRecipients()
287
308
method, but it returns a sorted list instead of a NotificationRecipientSet.
288
309
It too contains the question assignee.
289
310
 
290
311
    >>> indirect_subscribers = package_question.getIndirectSubscribers()
291
 
    >>> [person.displayname for person in indirect_subscribers]
292
 
    [u'Foo Bar', u'No Privileges Person', u'Ubuntu Team']
 
312
    >>> for person in indirect_subscribers:
 
313
    ...     print person.displayname
 
314
    Foo Bar
 
315
    No Privileges Person
 
316
    Ubuntu Team
293
317
 
294
 
Notifications are sent to the list of direct and indirect subscribers.
295
 
The notification recipients list can be obtained by using the
296
 
getRecipients() method.
 
318
Notifications are sent to the list of direct and indirect subscribers.  The
 
319
notification recipients list can be obtained by using the getRecipients()
 
320
method.
297
321
 
298
322
    >>> login('no-priv@canonical.com')
299
323
    >>> subscribers = firefox_question.getRecipients()
300
324
    >>> verifyObject(INotificationRecipientSet, subscribers)
301
325
    True
302
 
    >>> [person.displayname for person in subscribers]
303
 
    [u'No Privileges Person', u'Sample Person']
304
 
 
305
 
(More documentation on the question notifications can be found in
306
 
'answer-tracker-notifications.txt'.)
307
 
 
308
 
 
309
 
== Workflow ==
 
326
    >>> for person in subscribers:
 
327
    ...     print person.displayname
 
328
    No Privileges Person
 
329
    Sample Person
 
330
 
 
331
More documentation on the question notifications can be found in
 
332
`answer-tracker-notifications.txt`.
 
333
 
 
334
 
 
335
Workflow
 
336
========
310
337
 
311
338
A question status should not be manipulated directly but through the
312
339
workflow methods.
313
340
 
314
341
The complete question workflow is documented in
315
 
'answer-tracker-workflow.txt'.
316
 
 
317
 
== Bug Linking ==
 
342
`answer-tracker-workflow.txt`.
 
343
 
 
344
 
 
345
Bug linking
 
346
===========
318
347
 
319
348
Question implements the IBugLinkTarget interface which makes it possible
320
349
to link bug report to question.
323
352
    >>> verifyObject(IBugLinkTarget, firefox_question)
324
353
    True
325
354
 
326
 
(See ../interfaces/ftests/buglinktarget.txt for the documentation and
327
 
test of the IBugLinkTarget interface.)
328
 
 
329
 
When a bug is linked to a question, the question's owner is subscribed to
330
 
the bug.
331
 
 
332
 
    >>> from canonical.launchpad.interfaces import IBugSet
 
355
See ../../bugs/tests/buglinktarget.txt for the documentation and test of the
 
356
IBugLinkTarget interface.
 
357
 
 
358
When a bug is linked to a question, the question's owner is subscribed to the
 
359
bug.
 
360
 
 
361
    >>> from lp.bugs.interfaces.bug import IBugSet
333
362
    >>> bug7 = getUtility(IBugSet).get(7)
334
363
    >>> bug7.isSubscribed(firefox_question.owner)
335
364
    False
338
367
    >>> bug7.isSubscribed(firefox_question.owner)
339
368
    True
340
369
 
341
 
When the link is removed, the owner is unsubscribed:
 
370
When the link is removed, the owner is unsubscribed.
342
371
 
343
372
    >>> firefox_question.unlinkBug(bug7)
344
373
    <QuestionBug...>
345
374
    >>> bug7.isSubscribed(firefox_question.owner)
346
375
    False
347
376
 
348
 
== Unsupported Questions ==
349
 
 
350
 
While a Person may ask questions in his language of choice, that does
351
 
not mean that indirect subscribers (Answer Contacts) to an
352
 
IQuestionTarget speak that language. IQuestionTarget can return a list
353
 
Questions in languages that are not supported
 
377
 
 
378
Unsupported questions
 
379
=====================
 
380
 
 
381
While a Person may ask questions in his language of choice, that does not mean
 
382
that indirect subscribers (Answer Contacts) to an IQuestionTarget speak that
 
383
language.  IQuestionTarget can return a list Questions in languages that are
 
384
not supported.
354
385
 
355
386
    >>> unsupported_questions = firefox.searchQuestions(unsupported=True)
356
 
    >>> sorted([question.title for question in unsupported_questions])
 
387
    >>> sorted(question.title for question in unsupported_questions)
357
388
    [u'Problemas de Impress\xe3o no Firefox']
358
389
 
359
390
    >>> unsupported_questions = evolution_in_ubuntu.searchQuestions(
360
391
    ...     unsupported=True)
361
 
    >>> sorted([question.title for question in unsupported_questions])
 
392
    >>> sorted(question.title for question in unsupported_questions)
362
393
    []
363
394
 
364
395
    >>> warty_question_target = IQuestionTarget(ubuntu_warty)
365
396
    >>> unsupported_questions = warty_question_target.searchQuestions(
366
397
    ...     unsupported=True)
367
 
    >>> sorted([question.title for question in unsupported_questions])
 
398
    >>> sorted(question.title for question in unsupported_questions)
368
399
    [u'Problema al recompilar kernel con soporte smp (doble-n\xfacleo)',
369
400
     u'\u0639\u0643\u0633 \u0627\u0644\u062a\u063a\u064a\u064a\u0631...]
370