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 lp.services.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 lp.services.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 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 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 lp.services.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 lp.services.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 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