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
|
# Copyright 2009 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
# pylint: disable-msg=E0211,E0213
"""Interfaces for things which have Questions."""
__metaclass__ = type
__all__ = [
'IAnswersFrontPageSearchForm',
'IQuestionTarget',
'ISearchQuestionsForm',
]
from zope.interface import Interface
from zope.schema import (
Choice,
Int,
List,
Set,
TextLine,
)
from lazr.restful.declarations import (
call_with,
export_as_webservice_entry,
export_read_operation,
export_write_operation,
operation_for_version,
operation_parameters,
operation_returns_collection_of,
REQUEST_USER,
)
from lazr.restful.fields import Reference
from canonical.launchpad import _
from lp.answers.interfaces.questioncollection import (
ISearchableByQuestionOwner,
)
from lp.answers.enums import (
QuestionSort,
QuestionStatus,
QUESTION_STATUS_DEFAULT_SEARCH,
)
from lp.registry.interfaces.person import IPerson
from lp.services.fields import PublicPersonChoice
from lp.services.worlddata.interfaces.language import ILanguage
class IQuestionTargetPublic(ISearchableByQuestionOwner):
"""Methods that anonymous in user can access."""
@operation_parameters(
question_id=Int(title=_('Question Number'), required=True))
@export_read_operation()
@operation_for_version('devel')
def getQuestion(question_id):
"""Return the question by its id, if it is applicable to this target.
:question_id: A question id.
If there is no such question number for this target, return None
"""
@operation_parameters(
phrase=TextLine(title=_('A phrase'), required=True))
@operation_returns_collection_of(Interface)
@export_read_operation()
@operation_for_version('devel')
def findSimilarQuestions(phrase):
"""Return questions similar to phrase.
Return a list of question similar to the provided phrase. These
questions will be found using a fuzzy search. The list is
ordered from the most similar question to the least similar question.
:param phrase: A phrase such as the summary of a question.
"""
@operation_parameters(
language=Reference(ILanguage))
@operation_returns_collection_of(IPerson)
@export_read_operation()
@operation_for_version('devel')
def getAnswerContactsForLanguage(language):
"""Return the list of Persons that provide support for a language.
An answer contact supports questions in his preferred languages.
"""
def getAnswerContactRecipients(language):
"""Return an `INotificationRecipientSet` of answer contacts.
:language: an ILanguage or None. When language is none, all
answer contacts are returned.
Return an INotificationRecipientSet of the answer contacts and the
reason they are recipients of an email. The answer contacts are
selected by their language and the fact that they are answer contacts
for the QuestionTarget.
"""
@operation_returns_collection_of(ILanguage)
@export_read_operation()
@operation_for_version('devel')
def getSupportedLanguages():
"""Return a list of languages spoken by at the answer contacts.
An answer contact is considered to speak a given language if that
language is listed as one of his preferred languages.
"""
answer_contacts = List(
title=_("Answer Contacts"),
description=_(
"Persons that are willing to provide support for this target. "
"They receive email notifications about each new question as "
"well as for changes to any questions related to this target."),
value_type=PublicPersonChoice(vocabulary="ValidPersonOrTeam"))
direct_answer_contacts = List(
title=_("Direct Answer Contacts"),
description=_(
"IPersons that registered as answer contacts explicitely on "
"this target. (answer_contacts may include answer contacts "
"inherited from other context.)"),
value_type=PublicPersonChoice(vocabulary="ValidPersonOrTeam"))
class IQuestionTargetView(Interface):
"""Methods that logged in user can access."""
def newQuestion(owner, title, description, language=None,
datecreated=None):
"""Create a new question.
A new question is created with status OPEN.
The owner and all of the target answer contacts will be subscribed
to the question.
:owner: An IPerson.
:title: A string.
:description: A string.
:language: An ILanguage. If that parameter is omitted, the question
is assumed to be created in English.
:datecreated: A datetime object that will be used for the datecreated
attribute. Defaults to canonical.database.constants.UTC_NOW.
"""
def createQuestionFromBug(bug):
"""Create and return a Question from a Bug.
The bug's title and description are used as the question title and
description. The bug owner is the question owner. The question
is automatically linked to the bug.
Note that bug messages are copied to the question, but attachments
are not. The question is the same age as the bug, though its
datelastresponse attribute is current to signify the question is
active.
:bug: An IBug.
"""
@operation_parameters(
person=PublicPersonChoice(
title=_('The user or an administered team'), required=True,
vocabulary='ValidPersonOrTeam'))
@call_with(subscribed_by=REQUEST_USER)
@export_read_operation()
@operation_for_version('devel')
def canUserAlterAnswerContact(person, subscribed_by):
"""Can the user add or remove the answer contact.
Users can add or remove themselves or one of the teams they
administered.
:param person: The `IPerson` that is or will be an answer contact.
:param subscribed_by: The `IPerson` making the change.
"""
@operation_parameters(
person=PublicPersonChoice(
title=_('The user of an administered team'), required=True,
vocabulary='ValidPersonOrTeam'))
@call_with(subscribed_by=REQUEST_USER)
@export_write_operation()
@operation_for_version('devel')
def addAnswerContact(person, subscribed_by):
"""Add a new answer contact.
:param person: An `IPerson`.
:param subscribed_by: The user making the change.
:return: True if the person was added, False if the person already is
an answer contact.
:raises AddAnswerContactError: When the person or team does no have a
preferred language.
"""
@operation_parameters(
person=PublicPersonChoice(
title=_('The user of an administered team'), required=True,
vocabulary='ValidPersonOrTeam'))
@call_with(subscribed_by=REQUEST_USER)
@export_write_operation()
@operation_for_version('devel')
def removeAnswerContact(person, subscribed_by):
"""Remove an answer contact.
:param person: An `IPerson`.
:param subscribed_by: The user making the change.
:return: True if the person was removed, False if the person wasn't an
answer contact.
"""
class IQuestionTarget(IQuestionTargetPublic, IQuestionTargetView):
"""An object that can have a new question asked about it."""
export_as_webservice_entry(as_of='devel')
# These schemas are only used by browser/questiontarget.py and should really
# live there. See Bug #66950.
class ISearchQuestionsForm(Interface):
"""Schema for the search question form."""
search_text = TextLine(title=_('Search text'), required=False)
sort = Choice(title=_('Sort order'), required=True,
vocabulary=QuestionSort,
default=QuestionSort.RELEVANCY)
status = Set(title=_('Status'), required=False,
value_type=Choice(vocabulary=QuestionStatus),
default=set(QUESTION_STATUS_DEFAULT_SEARCH))
class IAnswersFrontPageSearchForm(ISearchQuestionsForm):
"""Schema for the Answers front page search form."""
scope = Choice(title=_('Search scope'), required=False,
vocabulary='DistributionOrProductOrProjectGroup')
|