launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #08256
[Merge] lp:~rvb/maas/power-param-api3 into lp:maas
Raphaël Badin has proposed merging lp:~rvb/maas/power-param-api3 into lp:maas with lp:~rvb/maas/power-param-api2 as a prerequisite.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~rvb/maas/power-param-api3/+merge/107627
Use node edit forms in API's node update method.
--
https://code.launchpad.net/~rvb/maas/power-param-api3/+merge/107627
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/power-param-api3 into lp:maas.
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2012-05-24 15:56:29 +0000
+++ src/maasserver/api.py 2012-05-28 16:30:30 +0000
@@ -83,9 +83,11 @@
PermissionDenied,
ValidationError,
)
+from django.forms.models import model_to_dict
from django.http import (
HttpResponse,
HttpResponseBadRequest,
+ QueryDict,
)
from django.shortcuts import (
get_object_or_404,
@@ -107,7 +109,10 @@
Unauthorized,
)
from maasserver.fields import validate_mac
-from maasserver.forms import NodeWithMACAddressesForm
+from maasserver.forms import (
+ get_node_edit_form,
+ NodeWithMACAddressesForm,
+ )
from maasserver.models import (
Config,
FileStorage,
@@ -362,13 +367,6 @@
)
-EDITABLE_NODE_FIELDS = (
- 'hostname',
- 'architecture',
- 'power_type',
- )
-
-
@api_operations
class NodeHandler(BaseHandler):
"""Manage individual Nodes."""
@@ -390,21 +388,28 @@
vocabulary `ARCHITECTURE`).
:type architecture: basestring
:param power_type: The new power type for this node (see
- vocabulary `POWER_TYPE`).
+ vocabulary `POWER_TYPE`). Only available to admin users.
:type power_type: basestring
"""
node = Node.objects.get_node_or_404(
system_id=system_id, user=request.user, perm=NODE_PERMISSION.EDIT)
- unknown_fields = set(request.data).difference(EDITABLE_NODE_FIELDS)
- if len(unknown_fields) != 0:
- raise PermissionDenied(
- "Unable to set field(s): %s. Allowed fields are: %s." % (
- ','.join(unknown_fields), ','.join(EDITABLE_NODE_FIELDS)))
- for key, value in request.data.items():
- setattr(node, key, value)
- node.save()
- return node
+ # Create a writable query dict.
+ data = QueryDict('').copy()
+ # Missing fields will be taken from the node's current values. This
+ # is to circumvent Django's ModelForm (form created from a model)
+ # default behaviour that requires all the fields to be defined.
+ data.update(model_to_dict(node))
+ # We can't use update here because data is a QueryDict and updates
+ # does not replaces the old values with the new as one would expect.
+ for k, v in request.data.items():
+ data[k] = v
+ Form = get_node_edit_form(request.user)
+ form = Form(data, instance=node)
+ if form.is_valid():
+ return form.save()
+ else:
+ raise ValidationError(form.errors)
def delete(self, request, system_id):
"""Delete a specific Node."""
=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py 2012-04-27 10:56:37 +0000
+++ src/maasserver/forms.py 2012-05-28 16:30:30 +0000
@@ -13,6 +13,7 @@
__all__ = [
"CommissioningForm",
"get_action_form",
+ "get_node_edit_form",
"HostnameFormField",
"NodeForm",
"MACAddressForm",
@@ -110,7 +111,8 @@
after_commissioning_action = forms.ChoiceField(
label="After commissioning",
- choices=NODE_AFTER_COMMISSIONING_ACTION_CHOICES)
+ choices=NODE_AFTER_COMMISSIONING_ACTION_CHOICES,
+ required=False)
class Meta:
model = Node
@@ -124,7 +126,8 @@
after_commissioning_action = forms.ChoiceField(
label="After commissioning",
- choices=NODE_AFTER_COMMISSIONING_ACTION_CHOICES)
+ choices=NODE_AFTER_COMMISSIONING_ACTION_CHOICES,
+ required=False)
class Meta:
model = Node
@@ -135,6 +138,13 @@
)
+def get_node_edit_form(user):
+ if user.is_superuser:
+ return UIAdminNodeEditForm
+ else:
+ return UINodeEditForm
+
+
class MACAddressForm(ModelForm):
class Meta:
model = MACAddress
=== modified file 'src/maasserver/tests/test_api.py'
--- src/maasserver/tests/test_api.py 2012-05-24 16:10:47 +0000
+++ src/maasserver/tests/test_api.py 2012-05-28 16:30:30 +0000
@@ -35,7 +35,6 @@
from fixtures import Fixture
from maasserver import api
from maasserver.api import (
- EDITABLE_NODE_FIELDS,
extract_constraints,
extract_oauth_key,
extract_oauth_key_from_auth_header,
@@ -73,7 +72,10 @@
NodeUserData,
)
from metadataserver.nodeinituser import get_node_init_user
-from provisioningserver.enum import POWER_TYPE
+from provisioningserver.enum import (
+ POWER_TYPE,
+ POWER_TYPE_CHOICES,
+ )
class APIv10TestMixin:
@@ -866,20 +868,55 @@
self.assertEqual(0, Node.objects.filter(hostname='diane').count())
self.assertEqual(1, Node.objects.filter(hostname='francis').count())
- def test_PUT_rejects_unknown_fields(self):
- node = factory.make_node(owner=self.logged_in_user)
+ def test_PUT_ignores_unknown_fields(self):
+ node = factory.make_node(
+ owner=self.logged_in_user,
+ after_commissioning_action=(
+ NODE_AFTER_COMMISSIONING_ACTION.DEFAULT))
field = factory.getRandomString()
response = self.client.put(
self.get_node_uri(node),
- {field: factory.getRandomString}
- )
-
- error_msg = (
- "Unable to set field(s): %s. Allowed fields are: %s." % (
- (field, ','.join(EDITABLE_NODE_FIELDS))))
- self.assertEqual(
- (httplib.FORBIDDEN, error_msg),
- (response.status_code, response.content))
+ {field: factory.getRandomString()}
+ )
+
+ self.assertEqual(httplib.OK, response.status_code)
+
+ def test_PUT_admin_can_change_power_type(self):
+ self.become_admin()
+ original_power_type = factory.getRandomChoice(
+ POWER_TYPE_CHOICES)
+ new_power_type = factory.getRandomChoice(
+ POWER_TYPE_CHOICES, but_not=original_power_type)
+ node = factory.make_node(
+ owner=self.logged_in_user,
+ power_type=original_power_type,
+ after_commissioning_action=(
+ NODE_AFTER_COMMISSIONING_ACTION.DEFAULT))
+ self.client.put(
+ self.get_node_uri(node),
+ {'power_type': new_power_type}
+ )
+
+ self.assertEqual(
+ new_power_type, reload_object(node).power_type)
+
+ def test_PUT_non_admin_cannot_change_power_type(self):
+ original_power_type = factory.getRandomChoice(
+ POWER_TYPE_CHOICES)
+ new_power_type = factory.getRandomChoice(
+ POWER_TYPE_CHOICES, but_not=original_power_type)
+ node = factory.make_node(
+ owner=self.logged_in_user,
+ power_type=original_power_type,
+ after_commissioning_action=(
+ NODE_AFTER_COMMISSIONING_ACTION.DEFAULT))
+ self.client.put(
+ self.get_node_uri(node),
+ {'power_type': new_power_type}
+ )
+
+ self.assertEqual(
+ original_power_type, reload_object(node).power_type)
def test_resource_uri_points_back_at_node(self):
# When a Node is returned by the API, the field 'resource_uri'
=== modified file 'src/maasserver/tests/test_forms.py'
--- src/maasserver/tests/test_forms.py 2012-05-05 09:47:03 +0000
+++ src/maasserver/tests/test_forms.py 2012-05-28 16:30:30 +0000
@@ -27,6 +27,7 @@
ConfigForm,
EditUserForm,
get_action_form,
+ get_node_edit_form,
HostnameFormField,
MACAddressForm,
NewUserCreationForm,
@@ -240,6 +241,14 @@
after_commissioning_action, node.after_commissioning_action)
self.assertEqual(power_type, node.power_type)
+ def test_get_node_edit_form_returns_UIAdminNodeEditForm_if_admin(self):
+ admin = factory.make_admin()
+ self.assertEqual(UIAdminNodeEditForm, get_node_edit_form(admin))
+
+ def test_get_node_edit_form_returns_UINodeEditForm_if_non_admin(self):
+ user = factory.make_user()
+ self.assertEqual(UINodeEditForm, get_node_edit_form(user))
+
class TestNodeActionForm(TestCase):
=== modified file 'src/maasserver/views/nodes.py'
--- src/maasserver/views/nodes.py 2012-04-27 08:11:29 +0000
+++ src/maasserver/views/nodes.py 2012-05-28 16:30:30 +0000
@@ -41,9 +41,8 @@
)
from maasserver.forms import (
get_action_form,
+ get_node_edit_form,
MACAddressForm,
- UIAdminNodeEditForm,
- UINodeEditForm,
)
from maasserver.messages import messaging
from maasserver.models import (
@@ -151,10 +150,7 @@
return node
def get_form_class(self):
- if self.request.user.is_superuser:
- return UIAdminNodeEditForm
- else:
- return UINodeEditForm
+ return get_node_edit_form(self.request.user)
def get_success_url(self):
return reverse('node-view', args=[self.get_object().system_id])