launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #06303
[Merge] lp:~rvb/maas/maas-oauth-user-page into lp:maas
Raphaël Badin has proposed merging lp:~rvb/maas/maas-oauth-user-page into lp:maas.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~rvb/maas/maas-oauth-user-page/+merge/92280
This branch adds a user preferences page composed of 4 tabs:
- a user profile tab (to change the user's first name, email, etc)
- a user API tab to display/regenerate the user's API tokens (consumer key, token key and token secret)
- a tab to change the user's password
- a tab to manage the user's ssh key (not yet done)
Notes:
- AccountHandler is the handler responsible for handling the API requests to manage the logged-in user.
- I've changed UserProfile.reset_authorisation_token to also return the consumer because we need to display the consumer's key in the UI.
--
https://code.launchpad.net/~rvb/maas/maas-oauth-user-page/+merge/92280
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~rvb/maas/maas-oauth-user-page into lp:maas.
=== modified file 'src/maasserver/api.py'
--- src/maasserver/api.py 2012-02-08 15:20:37 +0000
+++ src/maasserver/api.py 2012-02-09 14:53:18 +0000
@@ -351,6 +351,29 @@
return ('node_mac_handler', [node_system_id, mac_address])
+@api_operations
+class AccountHandler(BaseHandler):
+ """Manage the current logged-in user."""
+ allowed_methods = ('POST',)
+
+ @api_exported('reset_authorisation_token')
+ def reset_authorisation_token(self, request):
+ """Regenerate the token and the secret of the OAuth Token.
+
+ :return: A json dict with three keys: 'token_key',
+ 'token_secret' and 'consumer_key' (e.g.
+ {token_key: 's65244576fgqs', token_secret: 'qsdfdhv34',
+ consumer_key: '68543fhj854fg'}).
+
+ """
+ profile = request.user.get_profile()
+ consumer, token = profile.reset_authorisation_token()
+ return {
+ 'token_key': token.key, 'token_secret': token.secret,
+ 'consumer_key': consumer.key,
+ }
+
+
def generate_api_doc():
docs = (
generate_doc(NodesHandler),
=== modified file 'src/maasserver/forms.py'
--- src/maasserver/forms.py 2012-01-31 17:57:34 +0000
+++ src/maasserver/forms.py 2012-02-09 14:53:18 +0000
@@ -15,6 +15,7 @@
]
from django import forms
+from django.contrib.auth.models import User
from django.forms import ModelForm
from maasserver.macaddress import MACAddressFormField
from maasserver.models import (
@@ -85,3 +86,9 @@
for mac in self.cleaned_data['mac_addresses']:
node.add_mac_address(mac)
return node
+
+
+class ProfileForm(ModelForm):
+ class Meta:
+ model = User
+ fields = ('first_name', 'last_name', 'email')
=== modified file 'src/maasserver/models.py'
--- src/maasserver/models.py 2012-02-08 13:45:45 +0000
+++ src/maasserver/models.py 2012-02-09 14:53:18 +0000
@@ -290,8 +290,8 @@
"""Create (if necessary) and regenerate the keys for the Consumer and
the related Token of the OAuth authorisation.
- :return: The token that was reset.
- :rtype: piston.models.Token
+ :return: A tuple containing the Consumer and the Token that were reset.
+ :rtype: tuple
"""
consumer, _ = Consumer.objects.get_or_create(
@@ -306,7 +306,7 @@
user=self.user, token_type=Token.ACCESS, consumer=consumer,
defaults={'is_approved': True})
token.generate_random_codes()
- return token
+ return consumer, token
def get_authorisation_consumer(self):
"""Returns the OAuth Consumer attached to the related User_.
=== added file 'src/maasserver/static/img/maybe.png'
Binary files src/maasserver/static/img/maybe.png 1970-01-01 00:00:00 +0000 and src/maasserver/static/img/maybe.png 2012-02-09 14:53:18 +0000 differ
=== added file 'src/maasserver/static/js/prefs.js'
--- src/maasserver/static/js/prefs.js 1970-01-01 00:00:00 +0000
+++ src/maasserver/static/js/prefs.js 2012-02-09 14:53:18 +0000
@@ -0,0 +1,101 @@
+/* Copyright 2012 Canonical Ltd. This software is licensed under the
+ * GNU Affero General Public License version 3 (see the file LICENSE).
+ *
+ * Utilities for the user preferences page.
+ *
+ * @module Y.mass.prefs
+ */
+
+YUI.add('maas.prefs', function(Y) {
+
+Y.log('loading mass.prefs');
+var module = Y.namespace('maas.prefs');
+
+// Only used to mockup io in tests.
+module._io = Y;
+
+var TokenWidget = function() {
+ TokenWidget.superclass.constructor.apply(this, arguments);
+};
+
+TokenWidget.NAME = 'profile-widget';
+
+Y.extend(TokenWidget, Y.Widget, {
+
+ displayError: function(message) {
+ this.status_node.set('text', message);
+ },
+
+ initializer: function(cfg) {
+ this.regenerate_link = Y.Node.create('<a />')
+ .set('href', '#')
+ .set('id','regenerate_tokens')
+ .set('text', "Regenerate the tokens");
+ this.status_node = Y.Node.create('<span />')
+ .set('id','regenerate_error');
+ this.spinnerNode = Y.Node.create('<img />')
+ .set('src', MAAS_config.uris.statics + 'img/spinner.gif');
+ this.get('srcNode').one('#tokens_regeneration_placeholer')
+ .append(this.regenerate_link)
+ .append(this.status_node);
+ },
+
+ bindUI: function() {
+ var self = this;
+ this.regenerate_link.on('click', function(e) {
+ e.preventDefault();
+ self.regenerateTokens();
+ });
+ },
+
+ showSpinner: function() {
+ this.displayError('');
+ this.status_node.insert(this.spinnerNode, 'after');
+ },
+
+ hideSpinner: function() {
+ this.spinnerNode.remove();
+ },
+
+ regenerateTokens: function() {
+ var self = this;
+ var cfg = {
+ method: 'POST',
+ data: 'op=reset_authorisation_token',
+ sync: false,
+ on: {
+ start: Y.bind(self.showSpinner, self),
+ end: Y.bind(self.hideSpinner, self),
+ success: function(id, out) {
+ var token;
+ try {
+ token = JSON.parse(out.response);
+ }
+ catch(e) {
+ // Parsing error.
+ displayError('Unable to regenerate the tokens.');
+ }
+ self.get(
+ 'srcNode').one(
+ '#token_key').set('text', token.token_key);
+ self.get(
+ 'srcNode').one(
+ '#token_secret').set('text', token.token_secret);
+ self.get(
+ 'srcNode').one(
+ '#consumer_key').set('text', token.consumer_key);
+ },
+ failure: function(id, out) {
+ displayError('Unable to regenerate the tokens.');
+ }
+ }
+ };
+ var request = module._io.io(
+ MAAS_config.uris.account_handler, cfg);
+ }
+});
+
+module.TokenWidget = TokenWidget;
+
+}, '0.1', {'requires': ['widget', 'io']}
+);
=== added file 'src/maasserver/static/js/tests/test_prefs.html'
--- src/maasserver/static/js/tests/test_prefs.html 1970-01-01 00:00:00 +0000
+++ src/maasserver/static/js/tests/test_prefs.html 2012-02-09 14:53:18 +0000
@@ -0,0 +1,40 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
+ <head>
+ <title>Test maas.prefs</title>
+
+ <!-- YUI and test setup -->
+ <script type="text/javascript" src="../yui/tests/yui/yui.js"></script>
+ <script type="text/javascript" src="../testing/testrunner.js"></script>
+ <script type="text/javascript" src="../testing/testing.js"></script>
+ <!-- The module under test -->
+ <script type="text/javascript" src="../prefs.js"></script>
+ <!-- The test suite -->
+ <script type="text/javascript" src="test_prefs.js"></script>
+ <script type="text/javascript">
+ <!--
+ var MAAS_config = {
+ uris: {
+ statics: '/static/',
+ account_handler: '/api/accounts/prefs/'
+ }
+ };
+ // -->
+ </script>
+ </head>
+ <body>
+ <span id="suite">maas.prefs.tests</span>
+ <script type="text/x-template" id="api-template">
+ <div id="placeholder">
+ <div id="consumer_key">
+ </div>
+ <div id="token_key">
+ </div>
+ <div id="token_secret">
+ </div>
+ <div id="tokens_regeneration_placeholer">
+ </div>
+ </div>
+ </script>
+ </body>
+</html>
=== added file 'src/maasserver/static/js/tests/test_prefs.js'
--- src/maasserver/static/js/tests/test_prefs.js 1970-01-01 00:00:00 +0000
+++ src/maasserver/static/js/tests/test_prefs.js 2012-02-09 14:53:18 +0000
@@ -0,0 +1,76 @@
+/* Copyright 2012 Canonical Ltd. This software is licensed under the
+ * GNU Affero General Public License version 3 (see the file LICENSE).
+ */
+
+YUI({ useBrowserConsole: true }).add('maas.prefs.tests', function(Y) {
+
+Y.log('loading maas.prefs.tests');
+var namespace = Y.namespace('maas.prefs.tests');
+
+var module = Y.maas.prefs;
+var suite = new Y.Test.Suite("maas.prefs Tests");
+
+var api_template = Y.one('#api-template').getContent();
+
+suite.add(new Y.maas.testing.TestCase({
+ name: 'test-prefs',
+
+ setUp: function() {
+ Y.one("body").append(Y.Node.create(api_template));
+ },
+
+ testInitializer: function() {
+ var widget = new module.TokenWidget({srcNode: '#placeholder'});
+ this.addCleanup(function() { widget.destroy(); });
+ widget.render();
+ var regenerate_link = widget.get('srcNode').one('#regenerate_tokens');
+ Y.Assert.isNotNull(regenerate_link);
+ Y.Assert.areEqual(
+ "Regenerate the tokens", regenerate_link.get('text'));
+ var status_node = widget.get('srcNode').one('#regenerate_error');
+ Y.Assert.isNotNull(status_node);
+ },
+
+ testRegenerateTokensCall: function() {
+ var mockXhr = Y.Mock();
+ Y.Mock.expect(mockXhr, {
+ method: 'io',
+ args: [MAAS_config.uris.account_handler, Y.Mock.Value.Any]
+ });
+ this.mockIO(mockXhr, module);
+ var widget = new module.TokenWidget({srcNode: '#placeholder'});
+ this.addCleanup(function() { widget.destroy(); });
+ widget.render();
+ var link = widget.get('srcNode').one('#regenerate_tokens');
+ link.simulate('click');
+ Y.Mock.verify(mockXhr);
+ },
+
+ testRegenerateTokensUpdatesTokens: function() {
+ var mockXhr = new Y.Base();
+ mockXhr.io = function(url, cfg) {
+ var response = {
+ token_key: 'token_key', token_secret: 'token_secret',
+ consumer_key: 'consumer_key'};
+ cfg.on.success(3, {response: Y.JSON.stringify(response)});
+ };
+ this.mockIO(mockXhr, module);
+ var widget = new module.TokenWidget({srcNode: '#placeholder'});
+ widget.render();
+ var link = widget.get('srcNode').one('#regenerate_tokens');
+ link.simulate('click');
+ var src_node = widget.get('srcNode');
+ Y.Assert.areEqual('token_key', src_node.one('#token_key').get('text'));
+ Y.Assert.areEqual(
+ 'token_secret', src_node.one('#token_secret').get('text'));
+ Y.Assert.areEqual(
+ 'consumer_key', src_node.one('#consumer_key').get('text'));
+ }
+
+}));
+
+namespace.suite = suite;
+
+}, '0.1', {'requires': [
+ 'node-event-simulate', 'node', 'test', 'maas.testing', 'maas.prefs']}
+);
=== modified file 'src/maasserver/templates/maasserver/js-conf.html'
--- src/maasserver/templates/maasserver/js-conf.html 2012-02-03 14:39:50 +0000
+++ src/maasserver/templates/maasserver/js-conf.html 2012-02-09 14:53:18 +0000
@@ -8,7 +8,8 @@
var MAAS_config = {
uris: {
statics: '{{ STATIC_URL }}',
- nodes_handler: '{% url "nodes_handler" %}'
+ nodes_handler: '{% url "nodes_handler" %}',
+ account_handler: '{% url "account_handler" %}'
},
debug: {% if YUI_DEBUG %}true{% else %}false{% endif %}
};
@@ -19,4 +20,5 @@
</script>
<script src="{{ STATIC_URL }}js/node_add.js"></script>
<script src="{{ STATIC_URL }}js/node.js"></script>
+<script src="{{ STATIC_URL }}js/prefs.js"></script>
<script src="{{ STATIC_URL }}js/node_views.js"></script>
=== added file 'src/maasserver/templates/maasserver/prefs.html'
--- src/maasserver/templates/maasserver/prefs.html 1970-01-01 00:00:00 +0000
+++ src/maasserver/templates/maasserver/prefs.html 2012-02-09 14:53:18 +0000
@@ -0,0 +1,96 @@
+{% extends "maasserver/base.html" %}
+
+{% block nav-active-nodes %}active{% endblock %}
+
+{% block head %}
+ <script type="text/javascript">
+ <!--
+ YUI().use('tabview', 'maas.prefs', function (Y) {
+ var tabview = new Y.TabView({
+ srcNode: '#prefs'
+ });
+ tabview.render();
+ // Select the tab with index {{ tab }}.
+ var tab_index = parseInt('{{ tab }}');
+ var valid_tab_index = (
+ Y.Lang.isValue(tab_index) &&
+ tab_index >= 0 &&
+ tab_index < tabview.size());
+ if (valid_tab_index) {
+ tabview.selectChild(tab_index);
+ }
+
+ var profile_widget = new Y.maas.prefs.TokenWidget(
+ {'srcNode': Y.one('#api')});
+ profile_widget.render();
+ });
+ // -->
+ </script>
+{% endblock %}
+
+{% block content %}
+ <h2>User preferences for {{ user.username }}</h2>
+
+ <div id="prefs">
+ <ul>
+ <li><a href="#profile">Profile</a></li>
+ <li><a href="#api">API access</a></li>
+ <li><a href="#password">Change password</a></li>
+ <li><a href="#keys">Manage keys</a></li>
+ </ul>
+ <div>
+ <div id="profile">
+ <form action="." method="post">
+ {{ profile_form.as_p }}
+ <input type="hidden" name="profile_submit" value="1" />
+ <input type="submit" value="Save" />
+ </form>
+ </div>
+ <div id="api">
+ <p>
+ This is the required information you need to provide to a third
+ party application for it to be able to access the MaaS server
+ <a href="{% url 'api-doc' %}">API</a> on your behalf.
+ </p>
+ <p>
+ <label>API consumer key:
+ <img src="{{ STATIC_URL }}img/maybe.png"
+ title="Note that the API consumer secret is the empty string"
+ />
+ </label>
+ <div id="consumer_key">
+ {{ user.get_profile.get_authorisation_consumer.key }}
+ </div>
+ </p>
+ <p>
+ <label>API consumer secret:</label>
+ <div>'' (the empty string)</div>
+ </p>
+ <p>
+ <label>API token key:</label>
+ <div id="token_key">
+ {{ user.get_profile.get_authorisation_token.key }}
+ </div>
+ </p>
+ <p>
+ <label>API token secret:</label>
+ <div id="token_secret">
+ {{ user.get_profile.get_authorisation_token.secret }}
+ </div>
+ </p>
+ <p id="tokens_regeneration_placeholer" />
+ </div>
+ <div id="password">
+ <form action="." method="post">
+ {{ password_form.as_p }}
+ <input type="hidden" name="password_submit" value="1" />
+ <input type="submit" value="Change password" />
+ </form>
+ </div>
+ <div id="keys">
+ TODO manage keys.
+ </div>
+ </div>
+ </div>
+
+{% endblock %}
=== modified file 'src/maasserver/templates/maasserver/user-nav.html'
--- src/maasserver/templates/maasserver/user-nav.html 2012-01-25 13:33:25 +0000
+++ src/maasserver/templates/maasserver/user-nav.html 2012-02-09 14:53:18 +0000
@@ -1,7 +1,7 @@
<ul id="user-nav" class="nav">
{% if user.is_authenticated %}
<li><a href="{% url 'logout' %}">Logout</a></li>
- <li>{{ user.username }}</li>
+ <li><a href="{% url 'prefs' %}">{{ user.username }}</a></li>
{% else %}
<li><a href="{% url 'login' %}">Login</a></li>
{% endif %}
=== modified file 'src/maasserver/tests/test_models.py'
--- src/maasserver/tests/test_models.py 2012-02-08 13:45:45 +0000
+++ src/maasserver/tests/test_models.py 2012-02-09 14:53:18 +0000
@@ -144,6 +144,8 @@
user = factory.make_user()
consumers = Consumer.objects.filter(user=user, name=GENERIC_CONSUMER)
self.assertEqual([user], [consumer.user for consumer in consumers])
+ self.assertEqual(
+ consumers[0], user.get_profile().get_authorisation_consumer())
self.assertEqual(GENERIC_CONSUMER, consumers[0].name)
self.assertEqual(KEY_SIZE, len(consumers[0].key))
# The generic consumer has an empty secret.
@@ -154,6 +156,25 @@
user = factory.make_user()
tokens = Token.objects.filter(user=user)
self.assertEqual([user], [token.user for token in tokens])
+ self.assertEqual(
+ tokens[0], user.get_profile().get_authorisation_token())
self.assertIsInstance(tokens[0].key, unicode)
self.assertEqual(KEY_SIZE, len(tokens[0].key))
self.assertEqual(Token.ACCESS, tokens[0].token_type)
+
+ def test_token_reset(self):
+ # The OAuth Consumer keys and the related Token keys can be
+ # regenerated.
+ profile = factory.make_user().get_profile()
+ consumer_key = profile.get_authorisation_consumer().key
+ token_key = profile.get_authorisation_token().key
+ token_secret = profile.get_authorisation_token().secret
+
+ new_consumer, new_token = profile.reset_authorisation_token()
+ self.assertEqual('', new_consumer.secret)
+ self.assertNotEqual(consumer_key, new_consumer.key)
+ self.assertNotEqual(token_key, new_token.key)
+ self.assertNotEqual(token_secret, new_token.secret)
+
+ self.assertEqual(KEY_SIZE, len(new_token.key))
+ self.assertEqual(KEY_SIZE, len(new_consumer.key))
=== added file 'src/maasserver/tests/test_views.py'
--- src/maasserver/tests/test_views.py 1970-01-01 00:00:00 +0000
+++ src/maasserver/tests/test_views.py 2012-02-09 14:53:18 +0000
@@ -0,0 +1,96 @@
+# Copyright 2012 Canonical Ltd. This software is licensed under the
+# GNU Affero General Public License version 3 (see the file LICENSE).
+
+"""Test maasserver API."""
+
+from __future__ import (
+ print_function,
+ unicode_literals,
+ )
+
+__metaclass__ = type
+__all__ = []
+
+import httplib
+
+from django.contrib.auth.models import User
+from lxml.html import fromstring
+from maasserver.testing import LoggedInTestCase
+
+
+class UserPrefsViewTest(LoggedInTestCase):
+
+ def test_prefs_GET_profile(self):
+ # The preferences page (profile tab) displays a form with the
+ # user's personal information.
+ user = self.logged_in_user
+ user.first_name = 'Steve'
+ user.last_name = 'Bam'
+ user.save()
+ response = self.client.get('/accounts/prefs/')
+ doc = fromstring(response.content)
+ self.assertSequenceEqual(
+ ['User preferences for %s' % user.username],
+ [elem.text for elem in doc.cssselect('h2')])
+ self.assertSequenceEqual(
+ ['Bam'],
+ [elem.value for elem in
+ doc.cssselect('input#id_profile-last_name')])
+ self.assertSequenceEqual(
+ ['Steve'],
+ [elem.value for elem in
+ doc.cssselect('input#id_profile-first_name')])
+
+ def test_prefs_GET_api(self):
+ # The preferences page (api tab) displays the API access tokens.
+ user = self.logged_in_user
+ response = self.client.get('/accounts/prefs/?tab=1')
+ doc = fromstring(response.content)
+ # The consumer key and the token key/secret are displayed.
+ consumer = user.get_profile().get_authorisation_consumer()
+ token = user.get_profile().get_authorisation_token()
+ self.assertSequenceEqual(
+ [consumer.key],
+ [elem.text.strip() for elem in
+ doc.cssselect('div#consumer_key')])
+ self.assertSequenceEqual(
+ [token.key],
+ [elem.text.strip() for elem in
+ doc.cssselect('div#token_key')])
+ self.assertSequenceEqual(
+ [token.secret],
+ [elem.text.strip() for elem in
+ doc.cssselect('div#token_secret')])
+
+ def test_prefs_POST_profile(self):
+ # The preferences page allows the user the update its profile
+ # information.
+ response = self.client.post(
+ '/accounts/prefs/',
+ {
+ 'profile_submit': 1, 'profile-first_name': 'John',
+ 'profile-last_name': 'Doe', 'profile-email': 'jon@xxxxxxxxxxx'
+ })
+
+ self.assertEqual(httplib.FOUND, response.status_code)
+ user = User.objects.get(id=self.logged_in_user.id)
+ self.assertEqual('John', user.first_name)
+ self.assertEqual('Doe', user.last_name)
+ self.assertEqual('jon@xxxxxxxxxxx', user.email)
+
+ def test_prefs_POST_password(self):
+ # The preferences page allows the user to change his password.
+ self.logged_in_user.set_password('password')
+ old_pw = self.logged_in_user.password
+ response = self.client.post(
+ '/accounts/prefs/',
+ {
+ 'password_submit': 1,
+ 'password-old_password': 'test',
+ 'password-new_password1': 'new',
+ 'password-new_password2': 'new',
+ })
+ self.assertEqual(httplib.FOUND, response.status_code)
+ user = User.objects.get(id=self.logged_in_user.id)
+ # The password is SHA1ized, we just make sure that it has changed.
+ self.assertNotEqual(old_pw, user.password)
=== modified file 'src/maasserver/urls.py'
--- src/maasserver/urls.py 2012-02-08 15:20:37 +0000
+++ src/maasserver/urls.py 2012-02-09 14:53:18 +0000
@@ -21,6 +21,7 @@
redirect_to,
)
from maasserver.api import (
+ AccountHandler,
api_doc,
MaasAPIAuthentication,
NodeHandler,
@@ -33,11 +34,13 @@
logout,
NodeListView,
NodesCreateView,
+ userprefsview,
)
from piston.resource import Resource
# URLs accessible to anonymous users.
urlpatterns = patterns('maasserver.views',
+ url(r'^accounts/prefs/$', userprefsview, name='prefs'),
url(r'^accounts/login/$', login, name='login'),
url(r'^accounts/logout/$', logout, name='logout'),
url(
@@ -67,6 +70,7 @@
nodes_handler = Resource(NodesHandler, authentication=auth)
node_mac_handler = Resource(NodeMacHandler, authentication=auth)
node_macs_handler = Resource(NodeMacsHandler, authentication=auth)
+account_handler = Resource(AccountHandler, authentication=auth)
# API URLs accessible to anonymous users.
urlpatterns += patterns('',
@@ -86,4 +90,5 @@
r'^api/nodes/(?P<system_id>[\w\-]+)/$', node_handler,
name='node_handler'),
url(r'^api/nodes/$', nodes_handler, name='nodes_handler'),
+ url(r'^api/account/$', account_handler, name='account_handler'),
)
=== modified file 'src/maasserver/views.py'
--- src/maasserver/views.py 2012-01-27 08:52:59 +0000
+++ src/maasserver/views.py 2012-02-09 14:53:18 +0000
@@ -16,17 +16,22 @@
]
from django.contrib import messages
+from django.contrib.auth.forms import PasswordChangeForm as PasswordForm
from django.contrib.auth.views import logout as dj_logout
from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+from django.shortcuts import render_to_response
+from django.template import RequestContext
from django.views.generic import (
CreateView,
ListView,
)
+from maasserver.forms import ProfileForm
from maasserver.models import Node
def logout(request):
- messages.info(request, 'You have been logged out.')
+ messages.info(request, "You have been logged out.")
return dj_logout(request, next_page=reverse('login'))
@@ -44,3 +49,40 @@
def get_success_url(self):
return reverse('index')
+
+
+def userprefsview(request):
+ user = request.user
+ tab = request.GET.get('tab', 0)
+ # Process the profile update form.
+ if 'profile_submit' in request.POST:
+ tab = 0
+ profile_form = ProfileForm(
+ request.POST, instance=user, prefix='profile')
+ if profile_form.is_valid():
+ messages.info(request, "Profile updated.")
+ profile_form.save()
+ return HttpResponseRedirect('%s?tab=%d' % (reverse('prefs'), tab))
+ else:
+ profile_form = ProfileForm(instance=user, prefix='profile')
+
+ # Process the password change form.
+ if 'password_submit' in request.POST:
+ tab = 2
+ password_form = PasswordForm(
+ data=request.POST, user=user, prefix='password')
+ if password_form.is_valid():
+ messages.info(request, "Password updated.")
+ password_form.save()
+ return HttpResponseRedirect('%s?tab=%d' % (reverse('prefs'), tab))
+ else:
+ password_form = PasswordForm(user=user, prefix='password')
+
+ return render_to_response(
+ 'maasserver/prefs.html',
+ {
+ 'profile_form': profile_form,
+ 'password_form': password_form,
+ 'tab': tab # Tab index to display.
+ },
+ context_instance=RequestContext(request))
Follow ups