Merge lp:~cjohnston/qa-dashboard/user-edit-profile into lp:qa-dashboard

Proposed by Chris Johnston
Status: Merged
Approved by: Chris Johnston
Approved revision: 646
Merged at revision: 645
Proposed branch: lp:~cjohnston/qa-dashboard/user-edit-profile
Merge into: lp:qa-dashboard
Diff against target: 369 lines (+285/-6)
8 files modified
common/forms.py (+24/-0)
common/static/css/new-style.css (+84/-0)
common/templates/form.html (+26/-0)
common/templates/layout.html (+2/-1)
common/tests/__init__.py (+1/-0)
common/tests/test_profile.py (+116/-0)
common/views.py (+31/-5)
qa_dashboard/urls.py (+1/-0)
To merge this branch: bzr merge lp:~cjohnston/qa-dashboard/user-edit-profile
Reviewer Review Type Date Requested Status
Andy Doan (community) Approve
PS Jenkins bot continuous-integration Approve
Chris Johnston Needs Resubmitting
Review via email: mp+192043@code.launchpad.net

Commit message

Adds the ability for a user to edit their profile

To post a comment you must log in.
Revision history for this message
Andy Doan (doanac) wrote :

1) This looks a little shady:
216 + try:
217 + profile = UserProfile.objects.get(user=request.user)
218 + except UserProfile.DoesNotExist:
219 + profile = UserProfile(user=request.user)
Why would request.user not exist?

2) This seems too verbose:
165 - <li>Logged in as: <a href="http://launchpad.net/~{{ user.username }}">{{ user.username }}</a></li>
166 + <li>Logged in as: <strong><a href="http://launchpad.net/~{{ user.username }}">{{ user.username }}</a></strong></li>
167 + <li><a href="{% url 'edit_profile' %}">Edit profile</a>

Why not just have the logged-in link take you to your profile. ie:

  <li>Logged in as: <a href="{% url 'edit_profile' %}">{{ user.username }}</a></li>

Revision history for this message
Chris Johnston (cjohnston) wrote :

> 1) This looks a little shady:
> 216 + try:
> 217 + profile = UserProfile.objects.get(user=request.user)
> 218 + except UserProfile.DoesNotExist:
> 219 + profile = UserProfile(user=request.user)
> Why would request.user not exist?
>

It isn't trying request.user, it's trying the UserProfile, which a user may or may not have.

> 2) This seems too verbose:
> 165 - <li>Logged in as: <a href="http://launchpad.net/~{{ user.username
> }}">{{ user.username }}</a></li>
> 166 + <li>Logged in as: <strong><a href="http://launchpad.net/~{{
> user.username }}">{{ user.username }}</a></strong></li>
> 167 + <li><a href="{% url 'edit_profile' %}">Edit profile</a>
>
> Why not just have the logged-in link take you to your profile. ie:
>
> <li>Logged in as: <a href="{% url 'edit_profile' %}">{{ user.username
> }}</a></li>

Is that obvious enough? Would people know that they can click it to change their settings?

645. By Chris Johnston

Add ability for a user to edit their profile

Revision history for this message
Andy Doan (doanac) wrote :

> > 2) This seems too verbose:
> > 165 - <li>Logged in as: <a href="http://launchpad.net/~{{ user.username
> > }}">{{ user.username }}</a></li>
> > 166 + <li>Logged in as: <strong><a href="http://launchpad.net/~{{
> > user.username }}">{{ user.username }}</a></strong></li>
> > 167 + <li><a href="{% url 'edit_profile' %}">Edit profile</a>
> >
> > Why not just have the logged-in link take you to your profile. ie:
> >
> > <li>Logged in as: <a href="{% url 'edit_profile' %}">{{ user.username
> > }}</a></li>
>
> Is that obvious enough? Would people know that they can click it to change
> their settings?

Seemed so to me. Maybe we should ask a few other people though. I guess I just didn't see the point in a link that takes you to launchpad (and off our site)

Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:645
http://10.97.0.26:8080/job/dashboard-ci/210/
Executed test runs:

Click here to trigger a rebuild:
http://10.97.0.26:8080/job/dashboard-ci/210/rebuild

review: Approve (continuous-integration)
Revision history for this message
Joe Talbott (joetalbott) wrote :

On Mon, Oct 21, 2013 at 08:09:21PM -0000, Andy Doan wrote:
> > > 2) This seems too verbose:
> > > 165 - <li>Logged in as: <a href="http://launchpad.net/~{{ user.username
> > > }}">{{ user.username }}</a></li>
> > > 166 + <li>Logged in as: <strong><a href="http://launchpad.net/~{{
> > > user.username }}">{{ user.username }}</a></strong></li>
> > > 167 + <li><a href="{% url 'edit_profile' %}">Edit profile</a>
> > >
> > > Why not just have the logged-in link take you to your profile. ie:
> > >
> > > <li>Logged in as: <a href="{% url 'edit_profile' %}">{{ user.username
> > > }}</a></li>
> >
> > Is that obvious enough? Would people know that they can click it to change
> > their settings?
>
> Seemed so to me. Maybe we should ask a few other people though. I guess I just didn't see the point in a link that takes you to launchpad (and off our site)

I'm +1 for staying on our site.

646. By Chris Johnston

Update per review

Revision history for this message
Chris Johnston (cjohnston) :
review: Needs Resubmitting
Revision history for this message
PS Jenkins bot (ps-jenkins) wrote :

PASSED: Continuous integration, rev:646
http://10.97.0.26:8080/job/dashboard-ci/211/
Executed test runs:

Click here to trigger a rebuild:
http://10.97.0.26:8080/job/dashboard-ci/211/rebuild

review: Approve (continuous-integration)
Revision history for this message
Andy Doan (doanac) wrote :

+1 by me

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== added file 'common/forms.py'
2--- common/forms.py 1970-01-01 00:00:00 +0000
3+++ common/forms.py 2013-10-21 21:02:25 +0000
4@@ -0,0 +1,24 @@
5+# QA Dashboard
6+# Copyright 2012-2013 Canonical Ltd.
7+
8+# This program is free software: you can redistribute it and/or modify it
9+# under the terms of the GNU Affero General Public License version 3, as
10+# published by the Free Software Foundation.
11+
12+# This program is distributed in the hope that it will be useful, but
13+# WITHOUT ANY WARRANTY; without even the implied warranties of
14+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
15+# PURPOSE. See the GNU Affero General Public License for more details.
16+
17+# You should have received a copy of the GNU Affero General Public License
18+# along with this program. If not, see <http://www.gnu.org/licenses/>.
19+
20+from django.forms import ModelForm
21+
22+from common.models import UserProfile
23+
24+
25+class EditProfileForm(ModelForm):
26+ class Meta:
27+ model = UserProfile
28+ fields = ['use_private_url', ]
29
30=== added file 'common/static/css/new-style.css'
31--- common/static/css/new-style.css 1970-01-01 00:00:00 +0000
32+++ common/static/css/new-style.css 2013-10-21 21:02:25 +0000
33@@ -0,0 +1,84 @@
34+form fieldset {
35+ border-radius : 4px;
36+ background-repeat : no-repeat;
37+ background-color : #efeeec;
38+ background-position : -15px -15px;
39+ border : 0;
40+ margin-bottom : 8px;
41+ padding : 15px 20px 15px 94px;
42+}
43+form fieldset h3 {
44+ border-bottom : 1px dotted #dfdcd9;
45+ margin-bottom : 9px;
46+ padding-bottom : 10px;
47+}
48+form fieldset li:first-child {
49+ margin-top : 0;
50+}
51+form input[type="text"], form textarea {
52+ border-radius : 2px;
53+ background : #fff;
54+ border : #999 solid 1px;
55+ font-family : Ubuntu, Arial, "libra sans", sans-serif;
56+ display : block;
57+ padding : 4px;
58+}
59+form input:focus, form textarea:focus {
60+ border : #000 solid 1px;
61+}
62+form textarea[readonly="readonly"] {
63+ color : #999;
64+}
65+form input[type="checkbox"], form input[type="radio"] {
66+ margin : 0;
67+ width : auto;
68+ display: block;
69+}
70+form input[type="checkbox"] + label, form input[type="radio"] + label {
71+ display : inline;
72+ margin-left : 5px;
73+ vertical-align : middle;
74+ width : auto;
75+}
76+form input[type="submit"] {
77+ font-size : 1em;
78+ margin-bottom : 0.75em;
79+ border-radius : 4px;
80+ background-color : #dd4814;
81+ box-shadow : none;
82+ border : 0;
83+ color : #fff;
84+ display : block;
85+ padding : 10px 14px;
86+ text-shadow : none;
87+ width : auto;
88+ margin-bottom : 0;
89+}
90+form input[type="submit"]:hover {
91+ background : #dd4814;
92+}
93+form label {
94+ cursor : pointer;
95+ display : block;
96+ margin-bottom : 4px;
97+}
98+form label span {
99+ color : #df382c;
100+}
101+form ul {
102+ margin-left : 0;
103+}
104+form li {
105+ list-style : none outside none;
106+ margin-top : 14px;
107+}
108+form button[type="submit"] {
109+ border : 0;
110+ display : inline-block;
111+ font-family : Ubuntu, Arial, "libra sans", sans-serif;
112+ text-decoration : none;
113+ font-weight : 300;
114+}
115+form input[type="reset"] {
116+ display : none;
117+}
118
119=== added file 'common/templates/form.html'
120--- common/templates/form.html 1970-01-01 00:00:00 +0000
121+++ common/templates/form.html 2013-10-21 21:02:25 +0000
122@@ -0,0 +1,26 @@
123+{% extends "smokeng/smoke_layout.html" %}
124+
125+{% block page_name %}{{ form_title }}{%endblock %}
126+{% block content %}
127+<div class="grid_7">
128+ <form action="{{ request.path_info }}" method="POST">{% csrf_token %}
129+ <fieldset class='fieldset-1'>
130+ <h3>{{ form_title }}</h3>
131+ {{ form.as_ul }}
132+ <input type="submit" name="submit" value="Save" class="submit-button" />
133+ </fieldset>
134+ </form>
135+</div>
136+<script type="text/javascript"><!--
137+$(document).ready(function(){
138+ $('span[rel*=help]').colorTip({color:'orange'});
139+});
140+--></script>
141+<style>
142+form ul {
143+ height: 12em;
144+ overflow-y: scroll;
145+ overflow-x: hidden;
146+}
147+</style>
148+{% endblock %}
149
150=== modified file 'common/templates/layout.html'
151--- common/templates/layout.html 2013-10-11 20:43:53 +0000
152+++ common/templates/layout.html 2013-10-21 21:02:25 +0000
153@@ -9,6 +9,7 @@
154 <link href='{% static "css/grid.css" %}' rel='stylesheet' type='text/css' />
155 <link href='{% static "css/jquery.dataTables.css" %}' rel='stylesheet' type='text/css' />
156 <link href='{% static "css/style.css" %}' rel='stylesheet' type='text/css' />
157+ <link href='{% static "css/new-style.css" %}' rel='stylesheet' type='text/css' />
158 <title>{% block page_name %}{% endblock %} | Ubuntu CI Dashboard</title>
159 <script src='http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js' type="text/javascript"></script>
160 <script src='{% static "js/jquery.dataTables.min.js" %}' type="text/javascript"></script>
161@@ -23,7 +24,7 @@
162 <div class="header-login">
163 <ul>
164 {% if user.is_authenticated %}
165- <li>Logged in as: <a href="http://launchpad.net/~{{ user.username }}">{{ user.username }}</a></li>
166+ <li>Logged in as: <strong><a href="{% url 'edit_profile' %}">{{ user.username }}</a></strong></li>
167 {% if user.is_staff %}
168 <li><a href="/admin" title="Admin">Admin</a></li>
169 {% endif %}
170
171=== modified file 'common/tests/__init__.py'
172--- common/tests/__init__.py 2013-10-08 15:22:19 +0000
173+++ common/tests/__init__.py 2013-10-21 21:02:25 +0000
174@@ -20,3 +20,4 @@
175 from test_plugins import *
176 from test_buildids import *
177 from test_myserializer import *
178+from test_profile import *
179
180=== added file 'common/tests/test_profile.py'
181--- common/tests/test_profile.py 1970-01-01 00:00:00 +0000
182+++ common/tests/test_profile.py 2013-10-21 21:02:25 +0000
183@@ -0,0 +1,116 @@
184+# QA Dashboard
185+# Copyright 2012-2013 Canonical Ltd.
186+
187+# This program is free software: you can redistribute it and/or modify it
188+# under the terms of the GNU Affero General Public License version 3, as
189+# published by the Free Software Foundation.
190+
191+# This program is distributed in the hope that it will be useful, but
192+# WITHOUT ANY WARRANTY; without even the implied warranties of
193+# MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
194+# PURPOSE. See the GNU Affero General Public License for more details.
195+
196+# You should have received a copy of the GNU Affero General Public License
197+# along with this program. If not, see <http://www.gnu.org/licenses/>.
198+
199+from django import test as djangotest
200+from django.test.client import Client
201+from django.contrib.auth.models import User
202+from django.core.urlresolvers import reverse
203+
204+from smokeng.models import SmokeImage
205+
206+
207+class UserProfileTestCase(djangotest.TestCase):
208+
209+ def setUp(self):
210+ self.c = Client()
211+ self.user = User.objects.create(
212+ username='testuser',
213+ first_name='Test',
214+ last_name='User',
215+ )
216+ self.user.set_password('password')
217+ self.user.save()
218+
219+ self.image = SmokeImage.objects.create(
220+ release="saucy",
221+ arch="mako",
222+ variant="touch_mir",
223+ build_number="20130401",
224+ )
225+ self.assert_(self.image)
226+
227+ def tearDown(self):
228+ self.c.logout()
229+
230+ def login(self):
231+ logged_in = self.c.login(username='testuser', password='password')
232+ self.assertTrue(logged_in)
233+
234+ def _get_page_url(self):
235+ url = reverse(
236+ 'smokeng_overview',
237+ args=(
238+ self.image.release,
239+ ),
240+ )
241+ return url
242+
243+ def test_not_logged_in_no_edit_profile_link(self):
244+ """
245+ Test that a user who is not logged in does not have a link to edit
246+ their profile
247+ """
248+ response = self.c.get(self._get_page_url())
249+ self.assertContains(response, '/edit/', 0)
250+
251+ def test_logged_in_edit_profile_link(self):
252+ """
253+ Test that a user that is logged in has a link to edit their profile
254+ """
255+ self.login()
256+ response = self.c.get(self._get_page_url())
257+ self.assertContains(response, '/edit/', 1)
258+
259+ def test_not_logged_in_edit_profile_url(self):
260+ """
261+ Test that a user who tries to edit their profile without being logged
262+ in is redirected
263+ """
264+ response = self.c.get(reverse('edit_profile'))
265+ self.assertEquals(response.status_code, 302)
266+
267+ def test_edit_profile_url(self):
268+ """
269+ Test edit profile url
270+ """
271+ self.login()
272+ response = self.c.get(reverse('edit_profile'))
273+ self.assertEquals(response.status_code, 200)
274+ self.assertTemplateUsed(response, 'form.html')
275+
276+ def test_user_save_profile(self):
277+ """
278+ Tests that a user can update their profile
279+ """
280+ self.login()
281+ response = self.c.get(reverse('edit_profile'))
282+ self.assertEqual(response.status_code, 200)
283+
284+ # Define data to fill our the form
285+ use_private_url = True
286+ user = self.user
287+ csrf_token = '%s' % response.context['csrf_token']
288+
289+ # Post the form
290+ post = self.c.post(
291+ reverse('edit_profile'),
292+ data={
293+ 'csrfmiddlewaretoken': csrf_token,
294+ 'use_private_url': use_private_url,
295+ 'user': user,
296+ }
297+ )
298+
299+ self.assertEqual(post.status_code, 302)
300
301=== modified file 'common/views.py'
302--- common/views.py 2013-10-11 15:24:21 +0000
303+++ common/views.py 2013-10-21 21:02:25 +0000
304@@ -13,6 +13,7 @@
305 # You should have received a copy of the GNU Affero General Public License
306 # along with this program. If not, see <http://www.gnu.org/licenses/>.
307
308+from django.contrib.auth.decorators import login_required
309 from django.views.decorators.http import require_GET
310 from django.shortcuts import render_to_response
311 from django.template import RequestContext
312@@ -21,11 +22,9 @@
313 from django.http import HttpResponseRedirect
314
315 from common.plugin_helper import ExtensionManager
316-
317-from common.bread_crumbs import (
318- BreadCrumb,
319- BreadCrumbTrail,
320-)
321+from common.bread_crumbs import BreadCrumb, BreadCrumbTrail
322+from common.models import UserProfile
323+from common.forms import EditProfileForm
324
325
326 @BreadCrumb("CI Dashboard")
327@@ -51,3 +50,30 @@
328 def logout_view(request):
329 logout(request)
330 return HttpResponseRedirect('/')
331+
332+
333+@login_required
334+def edit_profile(request):
335+ form_title = 'Edit profile'
336+ try:
337+ profile = UserProfile.objects.get(user=request.user)
338+ except UserProfile.DoesNotExist:
339+ profile = UserProfile(user=request.user)
340+
341+ if request.method == 'POST':
342+ form = EditProfileForm(data=request.POST, instance=profile)
343+ if form.is_valid():
344+ form.save()
345+ return HttpResponseRedirect('/')
346+ else:
347+ form = EditProfileForm(instance=profile)
348+
349+ context = {
350+ 'form': form,
351+ 'form_title': form_title,
352+ }
353+ return render_to_response(
354+ 'form.html',
355+ context,
356+ RequestContext(request)
357+ )
358
359=== modified file 'qa_dashboard/urls.py'
360--- qa_dashboard/urls.py 2013-10-11 15:24:21 +0000
361+++ qa_dashboard/urls.py 2013-10-21 21:02:25 +0000
362@@ -23,6 +23,7 @@
363 urlpatterns = patterns(
364 '',
365 url(r'^$', 'common.views.index', name='index'),
366+ url(r'^edit/$', 'common.views.edit_profile', name='edit_profile'),
367 url(r'^admin/', include(admin.site.urls)),
368 url(r'^\+oops$', 'common.views.cause_oops', name='cause-oops'),
369 url(r'^logout$', 'common.views.logout_view', name='logout'),

Subscribers

People subscribed via source and target branches