~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
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
Answer Tracker FAQ Documents
============================


IFAQTarget
----------

The Answer Tracker offers features to manage answers to frequently asked
questions (also called FAQ). Like the regular questions, FAQ documents
are associated to distributions or products. The IFAQTarget interface is
provided by objects that can host FAQs.

    >>> from zope.security.proxy import removeSecurityProxy
    >>> from canonical.launchpad.webapp.testing import verifyObject

    >>> from lp.answers.interfaces.faqtarget import IFAQTarget
    >>> from lp.registry.interfaces.distribution import IDistributionSet
    >>> from lp.registry.interfaces.product import IProductSet

    >>> firefox = getUtility(IProductSet).getByName('firefox')

    # removeSecurityProxy() is needed because not all interface
    # attributes are available to everybody.

    >>> verifyObject(IFAQTarget, removeSecurityProxy(firefox))
    True

    >>> ubuntu = getUtility(IDistributionSet).getByName('ubuntu')
    >>> verifyObject(IFAQTarget, removeSecurityProxy(ubuntu))
    True

Any user who has 'launchpad.Moderate' on the project can create a new
FAQ. (That permission is granted to project's registrant and answer
contacts.)

    >>> login('test@canonical.com')

    >>> from canonical.launchpad.webapp.interfaces import ILaunchBag
    >>> sample_person = getUtility(ILaunchBag).user
    >>> print firefox.owner.displayname
    Sample Person

    >>> firefox_faq = firefox.newFAQ(
    ...     sample_person, 'How can I see the Fnords?',
    ...     "Install the Fnords highlighter extension and see the Fnords!")

(The complete description of IFAQTarget is available in
lib/canonical/launchpad/interfaces/ftests/faqtarget.txt)


IFAQTarget adapters
...................

Convenient adapters are available so that is possible to easily retrieve
a suitable IFAQTarget from objects that do not provide it directly.

It is possible to adapt an IDistributionSourcePackage to IFAQTarget,
(the distribution is really the appropriate IFAQTarget in this case):

    >>> mozilla_firefox = ubuntu.getSourcePackage('mozilla-firefox')
    >>> IFAQTarget.providedBy(mozilla_firefox)
    False

    >>> mozilla_firefox_faq_target = IFAQTarget(mozilla_firefox)
    >>> verifyObject(
    ...     IFAQTarget, removeSecurityProxy(mozilla_firefox_faq_target))
    True

Likewise, it is possible to adapt an ISourcePackage to IFAQTarget.

    >>> hoary = ubuntu.getSeries('hoary')
    >>> hoary_mozilla_firefox = hoary.getSourcePackage('mozilla-firefox')
    >>> IFAQTarget.providedBy(hoary_mozilla_firefox)
    False

    >>> hoary_firefox_faq_target = IFAQTarget(hoary_mozilla_firefox)
    >>> verifyObject(
    ...     IFAQTarget, removeSecurityProxy(hoary_firefox_faq_target))
    True

It is also possible to adapt an IQuestion into an IFAQTarget. This
adaptation returns the IFAQTarget that should be use for the question.

    >>> firefox_question = firefox.getQuestion(1)
    >>> IFAQTarget.providedBy(firefox_question)
    False

    >>> question_faq_target = IFAQTarget(firefox_question)
    >>> verifyObject(IFAQTarget, removeSecurityProxy(question_faq_target))
    True

Similarly, adapting an IFAQ into an IFAQTarget will return the target
that was used to create the IFAQ:

    >>> IFAQTarget(firefox_faq) == firefox
    True


IFAQCollection
--------------

The IFAQCollection interface is provided by objects that represents a
collection of FAQs. This interface can be used to retrieve and search
for FAQs. It is provided by product, distribution, and projects.

    >>> from lp.answers.interfaces.faqcollection import IFAQCollection
    >>> from lp.registry.interfaces.projectgroup import IProjectGroupSet
    >>> gnome = getUtility(IProjectGroupSet).getByName('gnome')
    >>> verifyObject(IFAQCollection, gnome)
    True

    >>> verifyObject(IFAQCollection, ubuntu)
    True

    >>> verifyObject(IFAQCollection, firefox)
    True

(The complete description of IFAQCollection is available in
lib/canonical/launchpad/interfaces/ftests/faqcollection.txt)


IFAQ
----

FAQ document provides the IFAQ interface.

    >>> from lp.answers.interfaces.faq import IFAQ
    >>> verifyObject(IFAQ, firefox_faq)
    True

The FAQ document information is available in the object attributes.

    >>> print firefox_faq.title
    How can I see the Fnords?

    >>> print firefox_faq.content
    Install the Fnords highlighter extension and see the Fnords!

    >>> print firefox_faq.owner.displayname
    Sample Person

The project that contains the FAQ is available using the target
attribute:

    >>> print firefox_faq.target.name
    firefox

IFAQ has two attributes used to track the last modification to the FAQ.
Initially, the last_updated_by and date_last_updated are not set.

    >>> print firefox_faq.last_updated_by
    None

    >>> print firefox_faq.date_last_updated
    None

When the FAQ is modified, the attributes are automatically updated.

    >>> from zope.event import notify
    >>> from zope.interface import providedBy
    >>> from lazr.lifecycle.event import ObjectModifiedEvent
    >>> from lazr.lifecycle.snapshot import Snapshot
    >>> old_faq = Snapshot(firefox_faq, providing=providedBy(firefox_faq))
    >>> firefox_faq.keywords = 'extension'
    >>> notify(ObjectModifiedEvent(
    ...     firefox_faq, old_faq, ['keywords'], user=sample_person))

    >>> print firefox_faq.last_updated_by.displayname
    Sample Person

    >>> firefox_faq.date_last_updated is not None
    True


IFAQ permissions
................

Only the project owners or answer contacts can edit an IFAQ.

    >>> from canonical.launchpad.webapp.authorization import check_permission

    >>> login(ANONYMOUS)
    >>> check_permission('launchpad.Edit', firefox_faq)
    False

So Sample Person (the project owner) has edit permission:

    >>> login('test@canonical.com')
    >>> print firefox.owner.displayname
    Sample Person

    >>> check_permission('launchpad.Edit', firefox_faq)
    True

Answer contacts can also edit FAQs:

    # An answer contact needs a preferred language.

    >>> from lp.services.worlddata.interfaces.language import ILanguageSet
    >>> no_priv = getUtility(ILaunchBag).user
    >>> no_priv.addLanguage(getUtility(ILanguageSet)['en'])
    >>> firefox.addAnswerContact(no_priv, no_priv)
    True

    >>> from canonical.launchpad.webapp.authorization import clear_cache
    >>> clear_cache()
    >>> check_permission('launchpad.Edit', firefox_faq)
    True


IFAQSet
-------

There is a global utility registered under the IFAQSet interface that
can be used to retrieve all FAQs posted on Launchpad.

    >>> from lp.answers.interfaces.faq import IFAQSet
    >>> faqset = getUtility(IFAQSet)
    >>> verifyObject(IFAQSet, faqset)
    True

It provides the IFAQCollection interface.

    >>> verifyObject(IFAQCollection, faqset)
    True

It can retrieve any FAQ by id using the getFAQ() method.

    >>> faqset.getFAQ(firefox_faq.id) == firefox_faq
    True

The searchFAQs() method can be used to find FAQs by keywords or owner.

    >>> from lp.registry.interfaces.person import IPersonSet
    >>> foo_bar = getUtility(IPersonSet).getByEmail('foo.bar@canonical.com')
    >>> for faq in faqset.searchFAQs(
    ...     search_text='java | flash', owner=foo_bar):
    ...     print '%s (%s)' % (faq.title, faq.target.displayname)
    How do I install plugins (Shockwave, QuickTime, etc.)? (Mozilla Firefox)
    How can I play MP3/Divx/DVDs/Quicktime/Realmedia files
        or view Flash/Java web pages (Ubuntu)

(See lib/canonical/launchpad/interfaces/ftests/faqcollection.txt for the
full interface description.)


Linking a FAQ to a question
---------------------------

An IFAQ can be used to answer a question. The linkFAQ() method on
IQuestion is used for that purpose. It takes as parameters the user
posting the answer, the FAQ containing the answer and a comment that
will be added to the question explaining the FAQ link.

    >>> fnord_question = firefox.newQuestion(
    ...     sample_person, 'Are there Fnords on the web?',
    ...     'Do Fnords also exists on the web?')

Any user can link an existing FAQ to a question.

    >>> login('no-priv@canonical.com')
    >>> no_priv = getUtility(ILaunchBag).user
    >>> message = fnord_question.linkFAQ(
    ...     no_priv, firefox_faq, 'See the FAQ.')

Once the FAQ is linked, the question is considered 'answered':

    >>> print message.action.title
    Answer

    >>> print fnord_question.status.title
    Answered

The 'faq' attribute contains the FAQ supposed to answer the question:

    >>> print fnord_question.faq.title
    How can I see the Fnords?

The FAQ's 'related_questions' attribute contains the questions that are
answered by the FAQ:

    # Flush the faq attribute change.

    >>> for question in firefox_faq.related_questions:
    ...     print question.title
    Are there Fnords on the web?

A FAQ can be linked to multiple question:

    >>> other_question = firefox.getQuestion(4)
    >>> message = other_question.linkFAQ(
    ...     no_priv, firefox_faq,
    ...     'If you lose focus and gets stuck it must be the fnords!')

    >>> print other_question.faq.title
    How can I see the Fnords?

    >>> print other_question.status.title
    Answered

    >>> for question in firefox_faq.related_questions:
    ...     print question.title
    Firefox loses focus and gets stuck
    Are there Fnords on the web?

The FAQ link can be changed or removed by using the linkFAQ() method
again:

    >>> message = other_question.linkFAQ(
    ...     no_priv, None, "This has nothing to do with Fnords.")
    >>> print other_question.faq
    None

After this, only the original question will remain linked to the FAQ.

    >>> for question in firefox_faq.related_questions:
    ...     print question.title
    Are there Fnords on the web?

That change is also considered an answer:

    >>> print message.action.title
    Answer

    >>> print other_question.status.title
    Answered

It is not possible to modify the faq attribute directly:

    >>> fnord_question.faq = None
    Traceback (most recent call last):
      ...
    ForbiddenAttribute: ...

And it is not allowed to call linkFAQ() when the FAQ is already linked:

    >>> message = fnord_question.linkFAQ(
    ...     no_priv, firefox_faq, 'See the FAQ.')
    Traceback (most recent call last):
      ...
    FAQTargetError: Cannot call linkFAQ() with already linked FAQ.

A FAQ can be linked to a 'solved' question, in which case, the status is
not changed.

    >>> login('foo.bar@canonical.com')
    >>> confirm_message = other_question.confirmAnswer(
    ...     "That answered my question.", answer=other_question.messages[-1])
    >>> print other_question.status.title
    Solved

    >>> login('no-priv@canonical.com')
    >>> message = other_question.linkFAQ(
    ...     no_priv, firefox_faq,
    ...     'If you look carefully, you will find the fnords!')
    >>> print message.action.title
    Comment

    >>> print other_question.status.title
    Solved