launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #07008
[Merge] lp:~rvb/maas/maas-aciton-feedback-bug-973272 into lp:maas
Raphaël Badin has proposed merging lp:~rvb/maas/maas-aciton-feedback-bug-973272 into lp:maas.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
Related bugs:
Bug #973272 in MAAS: "No UI feedback after node action"
https://bugs.launchpad.net/maas/+bug/973272
For more details, see:
https://code.launchpad.net/~rvb/maas/maas-aciton-feedback-bug-973272/+merge/100846
This branch adds the ability to publish a message when an action is performed.
I could have made this optional but I think it's really a good habit to publish a message each time an action is performed.
Note: technically I could have passed only the request to get_action_form but I'd rather pass the user *and* the request and have the request only used for message publishing.
--
https://code.launchpad.net/~rvb/maas/maas-aciton-feedback-bug-973272/+merge/100846
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/maas-aciton-feedback-bug-973272 into lp:maas.
=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py 2012-04-04 15:11:34 +0000
+++ src/maasserver/forms.py 2012-04-04 17:58:18 +0000
@@ -24,6 +24,7 @@
]
from django import forms
+from django.contrib import messages
from django.contrib.auth.forms import (
UserChangeForm,
UserCreationForm,
@@ -193,9 +194,6 @@
def start_node(node, user):
"""Start a node from the UI. It will have no meta_data."""
- # TODO: Provide some kind of feedback to the user. Starting a node
- # is fire-and-forget. From the user's point of view, it's as if all
- # that happens is the page reloading.
Node.objects.start_nodes([node.system_id], user)
@@ -231,11 +229,13 @@
'display': "Accept Enlisted node",
'permission': NODE_PERMISSION.ADMIN,
'execute': lambda node, user: Node.accept_enlistment(node),
+ 'message': "Node accepted into the pool."
},
{
'display': "Commission node",
'permission': NODE_PERMISSION.ADMIN,
'execute': lambda node, user: Node.start_commissioning(node, user),
+ 'message': "Node commissioned."
},
],
NODE_STATUS.READY: [
@@ -243,6 +243,7 @@
'display': "Start node",
'permission': NODE_PERMISSION.EDIT,
'execute': start_node,
+ 'message': "Node started."
},
],
NODE_STATUS.ALLOCATED: [
@@ -250,6 +251,7 @@
'display': "Start node",
'permission': NODE_PERMISSION.EDIT,
'execute': start_node,
+ 'message': "Node started."
},
],
}
@@ -263,6 +265,7 @@
"""
user = AnonymousUser()
+ request = None
# The name of the input button used with this form.
input_name = 'node_action'
@@ -275,7 +278,8 @@
# Create a convenient dict to fetch the action's name and
# the permission to be checked from the button name.
self.action_dict = {
- action['display']: (action['permission'], action['execute'])
+ action['display']: (
+ action['permission'], action['execute'], action['message'])
for action in self.action_buttons
}
@@ -294,28 +298,37 @@
action for action in NODE_ACTIONS.get(node.status, ())
if user.has_perm(action['permission'], node)]
+ def display_message(self, message):
+ if self.request is not None:
+ messages.add_message(self.request, messages.INFO, message)
+
def save(self):
action_name = self.data.get(self.input_name)
- permission, execute = self.action_dict.get(action_name, (None, None))
+ permission, execute, message = (
+ self.action_dict.get(action_name, (None, None, None)))
if execute is not None:
if not self.user.has_perm(permission, self.node):
raise PermissionDenied()
execute(self.node, self.user)
+ self.display_message(message)
else:
raise PermissionDenied()
-def get_action_form(user):
+def get_action_form(user, request=None):
"""Return a class derived from NodeActionForm for a specific user.
:param user: The user for which to build a form derived from
NodeActionForm.
:type user: :class:`django.contrib.auth.models.User`
+ :param request: An optional request object to publish action messages.
+ :type request: django.http.HttpRequest
:return: A form class derived from NodeActionForm.
:rtype: class:`django.forms.Form`
"""
return type(
- str("SpecificNodeActionForm"), (NodeActionForm,), {'user': user})
+ str("SpecificNodeActionForm"), (NodeActionForm,),
+ {'user': user, 'request': request})
class ProfileForm(ModelForm):
=== modified file 'src/maasserver/tests/test_forms.py'
--- src/maasserver/tests/test_forms.py 2012-04-04 15:11:34 +0000
+++ src/maasserver/tests/test_forms.py 2012-04-04 17:58:18 +0000
@@ -242,9 +242,10 @@
def test_NODE_ACTIONS_dict(self):
actions = sum(NODE_ACTIONS.values(), [])
+ keys = ['permission', 'display', 'execute', 'message']
self.assertThat(
[sorted(action.keys()) for action in actions],
- AllMatch(Equals(sorted(['permission', 'display', 'execute']))))
+ AllMatch(Equals(sorted(keys))))
class TestNodeActionForm(TestCase):
@@ -292,11 +293,7 @@
form = get_action_form(admin)(node)
self.assertItemsEqual(
- {"Accept Enlisted node": (
- 'accept_enlistment', NODE_PERMISSION.ADMIN),
- "Commission node": (
- 'start_commissioning', NODE_PERMISSION.ADMIN),
- },
+ ["Accept Enlisted node", "Commission node"],
form.action_dict)
def test_get_action_form_for_user(self):
@@ -311,7 +308,7 @@
def test_get_action_form_node_for_admin_save(self):
admin = factory.make_admin()
node = factory.make_node(status=NODE_STATUS.DECLARED)
- form = get_action_form(admin)(
+ form = get_action_form(admin, self.client.request)(
node, {NodeActionForm.input_name: "Accept Enlisted node"})
form.save()
=== modified file 'src/maasserver/tests/test_provisioning.py'
--- src/maasserver/tests/test_provisioning.py 2012-04-04 16:20:39 +0000
+++ src/maasserver/tests/test_provisioning.py 2012-04-04 17:58:18 +0000
@@ -20,7 +20,6 @@
from maasserver.exceptions import MAASAPIException
from maasserver.models import (
ARCHITECTURE,
- ARCHITECTURE_CHOICES,
Config,
Node,
NODE_AFTER_COMMISSIONING_ACTION,
=== modified file 'src/maasserver/tests/test_views.py'
--- src/maasserver/tests/test_views.py 2012-04-04 14:45:23 +0000
+++ src/maasserver/tests/test_views.py 2012-04-04 17:58:18 +0000
@@ -610,8 +610,7 @@
self.assertAttributes(node, params)
def test_view_node_admin_has_button_to_accept_enlistement(self):
- self.logged_in_user.is_superuser = True
- self.logged_in_user.save()
+ self.become_admin()
node = factory.make_node(status=NODE_STATUS.DECLARED)
node_link = reverse('node-view', args=[node.system_id])
response = self.client.get(node_link)
@@ -624,8 +623,7 @@
"Accept Enlisted node", [input.value for input in inputs])
def test_view_node_POST_admin_can_enlist_node(self):
- self.logged_in_user.is_superuser = True
- self.logged_in_user.save()
+ self.become_admin()
node = factory.make_node(status=NODE_STATUS.DECLARED)
node_link = reverse('node-view', args=[node.system_id])
response = self.client.post(
@@ -638,6 +636,21 @@
self.assertEqual(
NODE_STATUS.READY, reload_object(node).status)
+ def test_view_node_POST_admin_enlist_node_sees_message(self):
+ self.become_admin()
+ node = factory.make_node(status=NODE_STATUS.DECLARED)
+ node_link = reverse('node-view', args=[node.system_id])
+ self.client.post(
+ node_link,
+ data={
+ NodeActionForm.input_name: "Accept Enlisted node",
+ })
+ response = self.client.get(node_link)
+
+ self.assertEqual(
+ ['Node accepted into the pool.'],
+ [message.message for message in response.context['messages']])
+
def test_view_node_has_button_to_accept_enlistement_for_user(self):
# A simple user can't see the button to enlist a declared node.
node = factory.make_node(status=NODE_STATUS.DECLARED)
=== modified file 'src/maasserver/views.py'
--- src/maasserver/views.py 2012-04-04 14:45:23 +0000
+++ src/maasserver/views.py 2012-04-04 17:58:18 +0000
@@ -124,7 +124,7 @@
return node
def get_form_class(self):
- return get_action_form(self.request.user)
+ return get_action_form(self.request.user, self.request)
def get_context_data(self, **kwargs):
context = super(NodeView, self).get_context_data(**kwargs)