launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #07203
[Merge] lp:~rvb/maas/views-bug-973215-2 into lp:maas
Raphaël Badin has proposed merging lp:~rvb/maas/views-bug-973215-2 into lp:maas.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~rvb/maas/views-bug-973215-2/+merge/103076
This branch is the second in a series of branches that will refactor src/maasserver/views.py into modules in src/maasserver/views/.
This branch follows up on the work done in https://code.launchpad.net/~rvb/maas/views-bug-973215/+merge/103061.
It moves nodes views (dashboard, node list, node edit/view views) to
src/maasserver/views/nodes.py. It also moves the related tests in src/maasserver/tests/test_views_nodes.py.
--
https://code.launchpad.net/~rvb/maas/views-bug-973215-2/+merge/103076
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/views-bug-973215-2 into lp:maas.
=== modified file 'src/maasserver/testing/__init__.py'
--- src/maasserver/testing/__init__.py 2012-04-16 10:00:51 +0000
+++ src/maasserver/testing/__init__.py 2012-04-23 11:00:36 +0000
@@ -20,6 +20,7 @@
import os
from uuid import uuid1
+from lxml.html import fromstring
from provisioningserver.testing import fakeapi
# Current (singleton) fake provisioning API server.
@@ -106,3 +107,10 @@
path = os.path.join(
os.path.dirname(os.path.abspath(__file__)), '..', 'tests', filename)
return file(path).read()
+
+
+def get_content_links(response, element='#content'):
+ """Extract links from :class:`HttpResponse` #content element."""
+ doc = fromstring(response.content)
+ [content_node] = doc.cssselect(element)
+ return [elem.get('href') for elem in content_node.cssselect('a')]
=== modified file 'src/maasserver/tests/test_views.py'
--- src/maasserver/tests/test_views.py 2012-04-23 10:31:11 +0000
+++ src/maasserver/tests/test_views.py 2012-04-23 11:00:36 +0000
@@ -29,25 +29,20 @@
from maasserver import (
components,
messages,
- views,
)
from maasserver.components import register_persistent_error
-from maasserver.enum import (
- NODE_AFTER_COMMISSIONING_ACTION,
- NODE_STATUS,
- )
+from maasserver.enum import NODE_AFTER_COMMISSIONING_ACTION
from maasserver.exceptions import (
ExternalComponentException,
NoRabbit,
)
-from maasserver.forms import NodeActionForm
from maasserver.models import (
Config,
- Node,
SSHKey,
UserProfile,
)
from maasserver.testing import (
+ get_content_links,
get_data,
reload_object,
)
@@ -58,9 +53,12 @@
TestCase,
)
from maasserver.views import (
- get_longpoll_context,
get_yui_location,
HelpfulDeleteView,
+ nodes as nodes_views,
+ )
+from maasserver.views.nodes import (
+ get_longpoll_context,
NodeEdit,
)
from maastesting.rabbit import uses_rabbit_fixture
@@ -213,7 +211,7 @@
def test_get_longpoll_context_empty_if_rabbitmq_publish_is_none(self):
self.patch(settings, 'RABBITMQ_PUBLISH', None)
- self.patch(views, 'messaging', messages.get_messaging())
+ self.patch(nodes_views, 'messaging', messages.get_messaging())
self.assertEqual({}, get_longpoll_context())
def test_get_longpoll_context_returns_empty_if_rabbit_not_running(self):
@@ -229,7 +227,7 @@
def test_get_longpoll_context_empty_if_longpoll_url_is_None(self):
self.patch(settings, 'LONGPOLL_PATH', None)
- self.patch(views, 'messaging', messages.get_messaging())
+ self.patch(nodes_views, 'messaging', messages.get_messaging())
self.assertEqual({}, get_longpoll_context())
@uses_rabbit_fixture
@@ -237,10 +235,10 @@
longpoll = factory.getRandomString()
self.patch(settings, 'LONGPOLL_PATH', longpoll)
self.patch(settings, 'RABBITMQ_PUBLISH', True)
- self.patch(views, 'messaging', messages.get_messaging())
+ self.patch(nodes_views, 'messaging', messages.get_messaging())
context = get_longpoll_context()
self.assertItemsEqual(
- ['LONGPOLL_PATH', 'longpoll_queue'], list(context))
+ ['LONGPOLL_PATH', 'longpoll_queue'], context)
self.assertEqual(longpoll, context['LONGPOLL_PATH'])
@@ -554,6 +552,7 @@
self.logged_in_user.save()
+<<<<<<< TREE
def get_content_links(response, element='#content'):
"""Extract links from :class:`HttpResponse` #content element."""
doc = fromstring(response.content)
@@ -820,6 +819,8 @@
[message.message for message in response.context['messages']])
+=======
+>>>>>>> MERGE-SOURCE
class MAASExceptionHandledInView(LoggedInTestCase):
def test_raised_MAASException_redirects(self):
=== added file 'src/maasserver/tests/test_views_nodes.py'
--- src/maasserver/tests/test_views_nodes.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/tests/test_views_nodes.py 2012-04-23 11:00:36 +0000
@@ -0,0 +1,288 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test maasserver nodes views."""
+
+from __future__ import (
+ absolute_import,
+ print_function,
+ unicode_literals,
+ )
+
+__metaclass__ = type
+__all__ = []
+
+import httplib
+
+from django.core.urlresolvers import reverse
+from lxml.html import fromstring
+from maasserver.enum import NODE_STATUS
+from maasserver.forms import NodeActionForm
+from maasserver.models import Node
+from maasserver.testing import (
+ get_content_links,
+ reload_object,
+ )
+from maasserver.testing.enum import map_enum
+from maasserver.testing.factory import factory
+from maasserver.testing.testcase import LoggedInTestCase
+
+
+class NodeViewsTest(LoggedInTestCase):
+
+ def test_node_list_contains_link_to_node_view(self):
+ node = factory.make_node()
+ response = self.client.get(reverse('node-list'))
+ node_link = reverse('node-view', args=[node.system_id])
+ self.assertIn(node_link, get_content_links(response))
+
+ def test_node_list_displays_sorted_list_of_nodes(self):
+ # Nodes are sorted on the node list page, newest first.
+ nodes = [factory.make_node() for i in range(3)]
+ nodes.reverse()
+ # Modify one node to make sure that the default db ordering
+ # (by modification date) is not used.
+ node = nodes[1]
+ node.hostname = factory.getRandomString()
+ node.save()
+ response = self.client.get(reverse('node-list'))
+ node_links = [
+ reverse('node-view', args=[node.system_id])
+ for node in nodes]
+ self.assertEqual(
+ node_links,
+ [link for link in get_content_links(response)
+ if link.startswith('/nodes')])
+
+ def test_view_node_displays_node_info(self):
+ # The node page features the basic information about the node.
+ node = factory.make_node(owner=self.logged_in_user)
+ node_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_link)
+ doc = fromstring(response.content)
+ content_text = doc.cssselect('#content')[0].text_content()
+ self.assertIn(node.hostname, content_text)
+ self.assertIn(node.display_status(), content_text)
+ self.assertIn(self.logged_in_user.username, content_text)
+
+ def test_view_node_displays_node_info_no_owner(self):
+ # If the node has no owner, the Owner 'slot' does not exist.
+ node = factory.make_node()
+ node_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_link)
+ doc = fromstring(response.content)
+ content_text = doc.cssselect('#content')[0].text_content()
+ self.assertNotIn('Owner', content_text)
+
+ def test_view_node_displays_link_to_edit_if_user_owns_node(self):
+ node = factory.make_node(owner=self.logged_in_user)
+ node_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_link)
+ node_edit_link = reverse('node-edit', args=[node.system_id])
+ self.assertIn(node_edit_link, get_content_links(response))
+
+ def test_view_node_does_not_show_link_to_delete_node(self):
+ # Only admin users can delete nodes.
+ node = factory.make_node(owner=self.logged_in_user)
+ node_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_link)
+ node_delete_link = reverse('node-delete', args=[node.system_id])
+ self.assertNotIn(node_delete_link, get_content_links(response))
+
+ def test_user_cannot_delete_node(self):
+ node = factory.make_node(owner=self.logged_in_user)
+ node_delete_link = reverse('node-delete', args=[node.system_id])
+ response = self.client.get(node_delete_link)
+ self.assertEqual(httplib.FORBIDDEN, response.status_code)
+
+ def test_view_node_shows_message_for_commissioning_node(self):
+ statuses_with_message = (
+ NODE_STATUS.READY, NODE_STATUS.COMMISSIONING)
+ help_link = "https://wiki.ubuntu.com/ServerTeam/MAAS/AvahiBoot"
+ for status in map_enum(NODE_STATUS).values():
+ node = factory.make_node(status=status)
+ node_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_link)
+ links = get_content_links(response, '#flash-messages')
+ if status in statuses_with_message:
+ self.assertIn(help_link, links)
+ else:
+ self.assertNotIn(help_link, links)
+
+ def test_view_node_shows_link_to_delete_node_for_admin(self):
+ self.become_admin()
+ node = factory.make_node()
+ node_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_link)
+ node_delete_link = reverse('node-delete', args=[node.system_id])
+ self.assertIn(node_delete_link, get_content_links(response))
+
+ def test_admin_can_delete_nodes(self):
+ self.become_admin()
+ node = factory.make_node()
+ node_delete_link = reverse('node-delete', args=[node.system_id])
+ response = self.client.post(node_delete_link, {'post': 'yes'})
+ self.assertEqual(httplib.FOUND, response.status_code)
+ self.assertFalse(Node.objects.filter(id=node.id).exists())
+
+ def test_allocated_node_view_page_says_node_cannot_be_deleted(self):
+ self.become_admin()
+ node = factory.make_node(
+ status=NODE_STATUS.ALLOCATED, owner=factory.make_user())
+ node_view_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_view_link)
+ node_delete_link = reverse('node-delete', args=[node.system_id])
+
+ self.assertEqual(httplib.OK, response.status_code)
+ self.assertNotIn(node_delete_link, get_content_links(response))
+ self.assertIn(
+ "You cannot delete this node because it's in use.",
+ response.content)
+
+ def test_allocated_node_cannot_be_deleted(self):
+ self.become_admin()
+ node = factory.make_node(
+ status=NODE_STATUS.ALLOCATED, owner=factory.make_user())
+ node_delete_link = reverse('node-delete', args=[node.system_id])
+ response = self.client.get(node_delete_link)
+
+ self.assertEqual(httplib.FORBIDDEN, response.status_code)
+
+ def test_user_cannot_view_someone_elses_node(self):
+ node = factory.make_node(owner=factory.make_user())
+ node_view_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_view_link)
+ self.assertEqual(httplib.FORBIDDEN, response.status_code)
+
+ def test_user_cannot_edit_someone_elses_node(self):
+ node = factory.make_node(owner=factory.make_user())
+ node_edit_link = reverse('node-edit', args=[node.system_id])
+ response = self.client.get(node_edit_link)
+ self.assertEqual(httplib.FORBIDDEN, response.status_code)
+
+ def test_admin_can_view_someonelses_node(self):
+ self.become_admin()
+ node = factory.make_node(owner=factory.make_user())
+ node_view_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_view_link)
+ self.assertEqual(httplib.OK, response.status_code)
+
+ def test_admin_can_edit_someonelses_node(self):
+ self.become_admin()
+ node = factory.make_node(owner=factory.make_user())
+ node_edit_link = reverse('node-edit', args=[node.system_id])
+ response = self.client.get(node_edit_link)
+ self.assertEqual(httplib.OK, response.status_code)
+
+ def test_user_can_access_the_edition_page_for_his_nodes(self):
+ node = factory.make_node(owner=self.logged_in_user)
+ node_edit_link = reverse('node-edit', args=[node.system_id])
+ response = self.client.get(node_edit_link)
+ self.assertEqual(httplib.OK, response.status_code)
+
+ def test_user_can_edit_his_nodes(self):
+ node = factory.make_node(owner=self.logged_in_user)
+ node_edit_link = reverse('node-edit', args=[node.system_id])
+ params = {
+ 'hostname': factory.getRandomString(),
+ # XXX JeroenVermeulen 2012-04-12, bug=979539: re-enable.
+ #'after_commissioning_action': factory.getRandomEnum(
+ # NODE_AFTER_COMMISSIONING_ACTION),
+ }
+ response = self.client.post(node_edit_link, params)
+
+ node = reload_object(node)
+ self.assertEqual(httplib.FOUND, response.status_code)
+ self.assertAttributes(node, params)
+
+ 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)
+ node_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_link)
+ doc = fromstring(response.content)
+
+ self.assertEqual(0, len(doc.cssselect('form#node_actions input')))
+
+ def test_view_node_shows_console_output_if_error_set(self):
+ # When node.error is set but the node's status does not indicate an
+ # error condition, the contents of node.error are displayed as console
+ # output.
+ node = factory.make_node(
+ owner=self.logged_in_user, error=factory.getRandomString(),
+ status=NODE_STATUS.READY)
+ node_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_link)
+ console_output = fromstring(response.content).xpath(
+ '//h4[text()="Console output"]/following-sibling::span/text()')
+ self.assertEqual([node.error], console_output)
+
+ def test_view_node_shows_error_output_if_error_set(self):
+ # When node.error is set and the node's status indicates an error
+ # condition, the contents of node.error are displayed as error output.
+ node = factory.make_node(
+ owner=self.logged_in_user, error=factory.getRandomString(),
+ status=NODE_STATUS.FAILED_TESTS)
+ node_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_link)
+ error_output = fromstring(response.content).xpath(
+ '//h4[text()="Error output"]/following-sibling::span/text()')
+ self.assertEqual([node.error], error_output)
+
+ def test_view_node_shows_no_error_if_no_error_set(self):
+ node = factory.make_node(owner=self.logged_in_user)
+ node_link = reverse('node-view', args=[node.system_id])
+ response = self.client.get(node_link)
+ doc = fromstring(response.content)
+ content_text = doc.cssselect('#content')[0].text_content()
+ self.assertNotIn("Error output", content_text)
+
+ def test_view_node_POST_admin_can_start_commissioning_node(self):
+ self.become_admin()
+ node = factory.make_node(status=NODE_STATUS.DECLARED)
+ node_link = reverse('node-view', args=[node.system_id])
+ response = self.client.post(
+ node_link,
+ data={
+ NodeActionForm.input_name: "Accept & commission",
+ })
+ self.assertEqual(httplib.FOUND, response.status_code)
+ self.assertEqual(
+ NODE_STATUS.COMMISSIONING, reload_object(node).status)
+
+ def perform_action_and_get_node_page(self, node, action_name):
+ node_link = reverse('node-view', args=[node.system_id])
+ self.client.post(
+ node_link,
+ data={
+ NodeActionForm.input_name: action_name,
+ })
+ response = self.client.get(node_link)
+ return response
+
+ def test_start_commisionning_displays_message(self):
+ self.become_admin()
+ node = factory.make_node(status=NODE_STATUS.DECLARED)
+ response = self.perform_action_and_get_node_page(
+ node, "Accept & commission")
+ self.assertIn(
+ "Node commissioning started.",
+ [message.message for message in response.context['messages']])
+
+ def test_start_node_from_ready_displays_message(self):
+ node = factory.make_node(
+ status=NODE_STATUS.READY, owner=self.logged_in_user)
+ response = self.perform_action_and_get_node_page(
+ node, "Start node")
+ self.assertIn(
+ "Node started.",
+ [message.message for message in response.context['messages']])
+
+ def test_start_node_from_allocated_displays_message(self):
+ node = factory.make_node(
+ status=NODE_STATUS.ALLOCATED, owner=self.logged_in_user)
+ response = self.perform_action_and_get_node_page(
+ node, "Start node")
+ self.assertEqual(
+ ["Node started."],
+ [message.message for message in response.context['messages']])
=== modified file 'src/maasserver/urls.py'
--- src/maasserver/urls.py 2012-04-23 09:08:05 +0000
+++ src/maasserver/urls.py 2012-04-23 11:00:36 +0000
@@ -33,11 +33,6 @@
AccountsEdit,
AccountsView,
combo_view,
- NodeDelete,
- NodeEdit,
- NodeListView,
- NodesCreateView,
- NodeView,
settings,
settings_add_archive,
SSHKeyCreateView,
@@ -48,6 +43,12 @@
login,
logout,
)
+from maasserver.views.nodes import (
+ NodeDelete,
+ NodeEdit,
+ NodeListView,
+ NodeView,
+ )
def adminurl(regexp, view, *args, **kwargs):
@@ -55,7 +56,7 @@
return url(regexp, view, *args, **kwargs)
-# URLs accessible to anonymous users.
+## URLs accessible to anonymous users.
urlpatterns = patterns('maasserver.views',
url(
r'^%s' % re.escape(django_settings.YUI_COMBO_URL), combo_view,
@@ -70,7 +71,8 @@
name='favicon'),
)
-# URLs for logged-in users.
+## URLs for logged-in users.
+# Preferences views.
urlpatterns += patterns('maasserver.views',
url(r'^account/prefs/$', userprefsview, name='prefs'),
url(
@@ -79,7 +81,15 @@
url(
r'^account/prefs/sshkey/delete/(?P<keyid>\d*)/$',
SSHKeyDeleteView.as_view(), name='prefs-delete-sshkey'),
+ )
+
+# Logout view.
+urlpatterns += patterns('maasserver.views',
url(r'^accounts/logout/$', logout, name='logout'),
+)
+
+# Nodes views.
+urlpatterns += patterns('maasserver.views',
url(
r'^$',
NodeListView.as_view(template_name="maasserver/index.html"),
@@ -94,12 +104,11 @@
url(
r'^nodes/(?P<system_id>[\w\-]+)/delete/$', NodeDelete.as_view(),
name='node-delete'),
- url(
- r'^nodes/create/$', NodesCreateView.as_view(), name='node-create'),
)
-# URLs for admin users.
+## URLs for admin users.
+# Settings views.
urlpatterns += patterns('maasserver.views',
adminurl(r'^settings/$', settings, name='settings'),
adminurl(
=== modified file 'src/maasserver/views/__init__.py'
--- src/maasserver/views/__init__.py 2012-04-23 09:08:05 +0000
+++ src/maasserver/views/__init__.py 2012-04-23 11:00:36 +0000
@@ -16,10 +16,6 @@
"AccountsEdit",
"AccountsView",
"combo_view",
- "NodeListView",
- "NodesCreateView",
- "NodeView",
- "NodeEdit",
"settings",
"settings_add_archive",
"SSHKeyCreateView",
@@ -30,7 +26,6 @@
ABCMeta,
abstractmethod,
)
-from logging import getLogger
import os
from convoy.combo import (
@@ -58,41 +53,26 @@
render_to_response,
)
from django.template import RequestContext
-from django.utils.safestring import mark_safe
from django.views.generic import (
CreateView,
DeleteView,
DetailView,
- ListView,
- UpdateView,
)
from django.views.generic.base import TemplateView
from django.views.generic.detail import SingleObjectTemplateResponseMixin
from django.views.generic.edit import ModelFormMixin
-from maasserver.enum import (
- NODE_PERMISSION,
- NODE_STATUS,
- )
-from maasserver.exceptions import (
- CannotDeleteUserException,
- NoRabbit,
- )
+from maasserver.exceptions import CannotDeleteUserException
from maasserver.forms import (
AddArchiveForm,
CommissioningForm,
EditUserForm,
- get_action_form,
MAASAndNetworkForm,
NewUserCreationForm,
ProfileForm,
SSHKeyForm,
UbuntuForm,
- UIAdminNodeEditForm,
- UINodeEditForm,
)
-from maasserver.messages import messaging
from maasserver.models import (
- Node,
SSHKey,
UserProfile,
)
@@ -176,135 +156,6 @@
return HttpResponseRedirect(self.get_next_url())
-# Info message displayed on the node page for COMMISSIONING
-# or READY nodes.
-NODE_BOOT_INFO = mark_safe("""
-You can boot this node using Avahi enabled boot media or an
-adequately configured dhcp server, see
-<a href="https://wiki.ubuntu.com/ServerTeam/MAAS/AvahiBoot">
-https://wiki.ubuntu.com/ServerTeam/MAAS/AvahiBoot</a> for
-details.
-""")
-
-
-class NodeView(UpdateView):
-
- template_name = 'maasserver/node_view.html'
-
- context_object_name = 'node'
-
- def get_object(self):
- system_id = self.kwargs.get('system_id', None)
- node = Node.objects.get_node_or_404(
- system_id=system_id, user=self.request.user,
- perm=NODE_PERMISSION.VIEW)
- return node
-
- def get_form_class(self):
- return get_action_form(self.request.user, self.request)
-
- def get_context_data(self, **kwargs):
- context = super(NodeView, self).get_context_data(**kwargs)
- node = self.get_object()
- context['can_edit'] = self.request.user.has_perm(
- NODE_PERMISSION.EDIT, node)
- context['can_delete'] = self.request.user.has_perm(
- NODE_PERMISSION.ADMIN, node)
- if node.status in (NODE_STATUS.COMMISSIONING, NODE_STATUS.READY):
- messages.info(self.request, NODE_BOOT_INFO)
- context['error_text'] = (
- node.error if node.status == NODE_STATUS.FAILED_TESTS else None)
- context['status_text'] = (
- node.error if node.status != NODE_STATUS.FAILED_TESTS else None)
- return context
-
- def get_success_url(self):
- return reverse('node-view', args=[self.get_object().system_id])
-
-
-class NodeEdit(UpdateView):
-
- template_name = 'maasserver/node_edit.html'
-
- def get_object(self):
- system_id = self.kwargs.get('system_id', None)
- node = Node.objects.get_node_or_404(
- system_id=system_id, user=self.request.user,
- perm=NODE_PERMISSION.EDIT)
- return node
-
- def get_form_class(self):
- if self.request.user.is_superuser:
- return UIAdminNodeEditForm
- else:
- return UINodeEditForm
-
- def get_success_url(self):
- return reverse('node-view', args=[self.get_object().system_id])
-
-
-class NodeDelete(HelpfulDeleteView):
-
- template_name = 'maasserver/node_confirm_delete.html'
- context_object_name = 'node_to_delete'
- model = Node
-
- def get_object(self):
- system_id = self.kwargs.get('system_id', None)
- node = Node.objects.get_node_or_404(
- system_id=system_id, user=self.request.user,
- perm=NODE_PERMISSION.ADMIN)
- if node.status == NODE_STATUS.ALLOCATED:
- raise PermissionDenied()
- return node
-
- def get_next_url(self):
- return reverse('node-list')
-
- def name_object(self, obj):
- """See `HelpfulDeleteView`."""
- return "Node %s" % obj.system_id
-
-
-def get_longpoll_context():
- if messaging is not None and django_settings.LONGPOLL_PATH is not None:
- try:
- return {
- 'longpoll_queue': messaging.getQueue().name,
- 'LONGPOLL_PATH': django_settings.LONGPOLL_PATH,
- }
- except NoRabbit as e:
- getLogger('maasserver').warn(
- "Could not connect to RabbitMQ: %s", e)
- return {}
- else:
- return {}
-
-
-class NodeListView(ListView):
-
- context_object_name = "node_list"
-
- def get_queryset(self):
- # Return node list sorted, newest first.
- return Node.objects.get_nodes(
- user=self.request.user,
- perm=NODE_PERMISSION.VIEW).order_by('-id')
-
- def get_context_data(self, **kwargs):
- context = super(NodeListView, self).get_context_data(**kwargs)
- context.update(get_longpoll_context())
- return context
-
-
-class NodesCreateView(CreateView):
-
- model = Node
-
- def get_success_url(self):
- return reverse('index')
-
-
class SSHKeyCreateView(CreateView):
form_class = SSHKeyForm
=== added file 'src/maasserver/views/nodes.py'
--- src/maasserver/views/nodes.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/views/nodes.py 2012-04-23 11:00:36 +0000
@@ -0,0 +1,163 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Nodes views."""
+
+from __future__ import (
+ absolute_import,
+ print_function,
+ unicode_literals,
+ )
+
+__metaclass__ = type
+__all__ = [
+ 'NodeListView',
+ 'NodeView',
+ 'NodeEdit',
+ ]
+
+from logging import getLogger
+
+from django.conf import settings as django_settings
+from django.contrib import messages
+from django.core.exceptions import PermissionDenied
+from django.core.urlresolvers import reverse
+from django.utils.safestring import mark_safe
+from django.views.generic import (
+ ListView,
+ UpdateView,
+ )
+from maasserver.enum import (
+ NODE_PERMISSION,
+ NODE_STATUS,
+ )
+from maasserver.exceptions import NoRabbit
+from maasserver.forms import (
+ get_action_form,
+ UIAdminNodeEditForm,
+ UINodeEditForm,
+ )
+from maasserver.messages import messaging
+from maasserver.models import Node
+from maasserver.views import HelpfulDeleteView
+
+
+def get_longpoll_context():
+ if messaging is not None and django_settings.LONGPOLL_PATH is not None:
+ try:
+ return {
+ 'longpoll_queue': messaging.getQueue().name,
+ 'LONGPOLL_PATH': django_settings.LONGPOLL_PATH,
+ }
+ except NoRabbit as e:
+ getLogger('maasserver').warn(
+ "Could not connect to RabbitMQ: %s", e)
+ return {}
+ else:
+ return {}
+
+
+class NodeListView(ListView):
+
+ context_object_name = "node_list"
+
+ def get_queryset(self):
+ # Return node list sorted, newest first.
+ return Node.objects.get_nodes(
+ user=self.request.user,
+ perm=NODE_PERMISSION.VIEW).order_by('-id')
+
+ def get_context_data(self, **kwargs):
+ context = super(NodeListView, self).get_context_data(**kwargs)
+ context.update(get_longpoll_context())
+ return context
+
+
+# Info message displayed on the node page for COMMISSIONING
+# or READY nodes.
+NODE_BOOT_INFO = mark_safe("""
+You can boot this node using Avahi enabled boot media or an
+adequately configured dhcp server, see
+<a href="https://wiki.ubuntu.com/ServerTeam/MAAS/AvahiBoot">
+https://wiki.ubuntu.com/ServerTeam/MAAS/AvahiBoot</a> for
+details.
+""")
+
+
+class NodeView(UpdateView):
+
+ template_name = 'maasserver/node_view.html'
+
+ context_object_name = 'node'
+
+ def get_object(self):
+ system_id = self.kwargs.get('system_id', None)
+ node = Node.objects.get_node_or_404(
+ system_id=system_id, user=self.request.user,
+ perm=NODE_PERMISSION.VIEW)
+ return node
+
+ def get_form_class(self):
+ return get_action_form(self.request.user, self.request)
+
+ def get_context_data(self, **kwargs):
+ context = super(NodeView, self).get_context_data(**kwargs)
+ node = self.get_object()
+ context['can_edit'] = self.request.user.has_perm(
+ NODE_PERMISSION.EDIT, node)
+ context['can_delete'] = self.request.user.has_perm(
+ NODE_PERMISSION.ADMIN, node)
+ if node.status in (NODE_STATUS.COMMISSIONING, NODE_STATUS.READY):
+ messages.info(self.request, NODE_BOOT_INFO)
+ context['error_text'] = (
+ node.error if node.status == NODE_STATUS.FAILED_TESTS else None)
+ context['status_text'] = (
+ node.error if node.status != NODE_STATUS.FAILED_TESTS else None)
+ return context
+
+ def get_success_url(self):
+ return reverse('node-view', args=[self.get_object().system_id])
+
+
+class NodeEdit(UpdateView):
+
+ template_name = 'maasserver/node_edit.html'
+
+ def get_object(self):
+ system_id = self.kwargs.get('system_id', None)
+ node = Node.objects.get_node_or_404(
+ system_id=system_id, user=self.request.user,
+ perm=NODE_PERMISSION.EDIT)
+ return node
+
+ def get_form_class(self):
+ if self.request.user.is_superuser:
+ return UIAdminNodeEditForm
+ else:
+ return UINodeEditForm
+
+ def get_success_url(self):
+ return reverse('node-view', args=[self.get_object().system_id])
+
+
+class NodeDelete(HelpfulDeleteView):
+
+ template_name = 'maasserver/node_confirm_delete.html'
+ context_object_name = 'node_to_delete'
+ model = Node
+
+ def get_object(self):
+ system_id = self.kwargs.get('system_id', None)
+ node = Node.objects.get_node_or_404(
+ system_id=system_id, user=self.request.user,
+ perm=NODE_PERMISSION.ADMIN)
+ if node.status == NODE_STATUS.ALLOCATED:
+ raise PermissionDenied()
+ return node
+
+ def get_next_url(self):
+ return reverse('node-list')
+
+ def name_object(self, obj):
+ """See `HelpfulDeleteView`."""
+ return "Node %s" % obj.system_id