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
|
# Copyright 2009-2011 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
"""A view for changing the owner or registrant of an object.
This view needs to be refactored to use the Launchpad form infrastructure.
See bug 151161.
"""
__metaclass__ = type
__all__ = ["ObjectReassignmentView"]
from zope.app.form.interfaces import (
ConversionError,
WidgetInputError,
)
from zope.component import getUtility
from zope.formlib.form import FormFields
from zope.schema import Choice
from zope.schema.vocabulary import (
SimpleTerm,
SimpleVocabulary,
)
from canonical.launchpad import _
from canonical.launchpad.webapp import canonical_url
from lp.app.browser.launchpadform import (
action,
custom_widget,
LaunchpadFormView,
)
from lp.app.validators.name import valid_name
from lp.app.widgets.itemswidgets import LaunchpadRadioWidget
from lp.registry.interfaces.person import (
IObjectReassignment,
IPersonSet,
)
class ObjectReassignmentView(LaunchpadFormView):
"""A view class used when reassigning an object that implements IHasOwner.
By default we assume that the owner attribute is IHasOwner.owner and the
vocabulary for the owner widget is ValidPersonOrTeam (which is the one
used in IObjectReassignment). If any object has special needs, it'll be
necessary to subclass ObjectReassignmentView and redefine the schema
and/or ownerOrMaintainerAttr attributes.
Subclasses can also specify a callback to be called after the reassignment
takes place. This callback must accept three arguments (in this order):
the object whose owner is going to be changed, the old owner and the new
owner.
Also, if the object for which you're using this view doesn't have a
displayname or name attribute, you'll have to subclass it and define the
contextName property in your subclass.
"""
ownerOrMaintainerAttr = 'owner'
ownerOrMaintainerName = 'owner'
# Called after changing the owner if it is overridden in a subclass.
callback = None
schema = IObjectReassignment
custom_widget('existing', LaunchpadRadioWidget)
@property
def label(self):
"""The form label."""
return 'Change the %s of %s' % (
self.ownerOrMaintainerName, self.contextName)
page_title = label
def setUpFields(self):
super(ObjectReassignmentView, self).setUpFields()
self.form_fields = FormFields(
self.form_fields, self.auto_create_team_field)
@property
def auto_create_team_field(self):
terms = [
SimpleTerm('existing', token='existing',
title='An existing person or team'),
SimpleTerm('new', token='new',
title="A new team I'm creating here"),
]
return Choice(
__name__='existing',
title=_('This is'),
source=SimpleVocabulary(terms),
default='existing',
description=_(
"The new team's name must begin with a lower-case letter "
"or number, and contain only letters, numbers, dots, hyphens, "
"or plus signs."))
@property
def ownerOrMaintainer(self):
return getattr(self.context, self.ownerOrMaintainerAttr)
@property
def contextName(self):
return self.context.displayname or self.context.name
@property
def next_url(self):
return canonical_url(self.context)
cancel_url = next_url
@property
def owner_widget(self):
return self.widgets['owner']
@action("Change", name="change")
def changeOwner(self, action, data):
"""Change the owner of self.context to the one choosen by the user."""
newOwner = data['owner']
oldOwner = getattr(self.context, self.ownerOrMaintainerAttr)
setattr(self.context, self.ownerOrMaintainerAttr, newOwner)
if callable(self.callback):
self.callback(self.context, oldOwner, newOwner)
def validateOwner(self, new_owner):
"""Check whether the new owner is acceptable for the context object.
If it's not acceptable, display an error by calling:
self.setFieldError(self.ownerOrMaintainerName, 'some error info')
"""
pass
def _validate(self, action, data):
"""Override _validate() method."""
# Don't let widgets validate themselves, just call validate().
self.validate(data)
if len(self.errors) > 0:
return self.errors
self.validate_widgets(data)
return self.errors
def validate(self, data):
"""Create new team if necessary."""
personset = getUtility(IPersonSet)
request = self.request
owner_name = request.form.get(self.owner_widget.name)
if not owner_name:
self.setFieldError(
'owner',
"You have to specify the name of the person/team that's "
"going to be the new %s." % self.ownerOrMaintainerName)
return None
if request.form.get('field.existing') == 'existing':
try:
# By getting the owner using getInputValue() we make sure
# it's valid according to the vocabulary of self.schema's
# owner widget.
owner = self.owner_widget.getInputValue()
except WidgetInputError:
self.setFieldError(
'owner',
"The person/team named '%s' is not a valid owner for %s."
% (owner_name, self.contextName))
return None
except ConversionError:
self.setFieldError(
self.ownerOrMaintainerName,
"There's no person/team named '%s' in Launchpad."
% owner_name)
return None
else:
if personset.getByName(owner_name):
self.setFieldError(
'owner',
"There's already a person/team with the name '%s' in "
"Launchpad. Please choose a different name or select "
"the option to make that person/team the new owner, "
"if that's what you want." % owner_name)
return None
if not valid_name(owner_name):
self.setFieldError(
'owner',
"'%s' is not a valid name for a team. Please make sure "
"it contains only the allowed characters and no spaces."
% owner_name)
return None
owner = personset.newTeam(
self.user, owner_name, owner_name.capitalize())
self.validateOwner(owner)
|