Merge lp:~salgado/offspring/project-ssh-key-ui into lp:offspring

Proposed by Guilherme Salgado
Status: Work in progress
Proposed branch: lp:~salgado/offspring/project-ssh-key-ui
Merge into: lp:offspring
Prerequisite: lp:~salgado/offspring/private-project-requires-owner
Diff against target: 722 lines (+488/-36)
10 files modified
lib/offspring/web/media/js/jquery.placeholder.js (+106/-0)
lib/offspring/web/queuemanager/admin.py (+5/-3)
lib/offspring/web/queuemanager/forms.py (+35/-6)
lib/offspring/web/queuemanager/models.py (+38/-4)
lib/offspring/web/queuemanager/tests/test_views.py (+112/-1)
lib/offspring/web/queuemanager/views.py (+46/-2)
lib/offspring/web/templates/queuemanager/project_create.html (+58/-18)
lib/offspring/web/templates/queuemanager/project_edit.html (+16/-2)
lib/offspring/web/templates/queuemanager/project_edit_credentials.html (+70/-0)
lib/offspring/web/urls.py (+2/-0)
To merge this branch: bzr merge lp:~salgado/offspring/project-ssh-key-ui
Reviewer Review Type Date Requested Status
CE Infrastructure Pending
Offspring Committers Pending
Review via email: mp+88259@code.launchpad.net

Description of the change

This branch adds the UI for setting the private SSH key and LP user ID. It uses a jquery plugin to display some help text inside a text area. Because of that it uses also the jquery that is included in Django (for the admin UI).

Also, the view code is not as simple as I'd like because we want to hide the SSH key when a user comes to the +editcredentials page after a key was set.

To post a comment you must log in.
118. By Guilherme Salgado

Merge trunk

119. By Guilherme Salgado

merge trunk

Unmerged revisions

119. By Guilherme Salgado

merge trunk

118. By Guilherme Salgado

Merge trunk

117. By Guilherme Salgado

Make it possible to set per-project SSH key and LP user to use when fetching private branches from Launchpad

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== added file 'lib/offspring/web/media/js/jquery.placeholder.js'
--- lib/offspring/web/media/js/jquery.placeholder.js 1970-01-01 00:00:00 +0000
+++ lib/offspring/web/media/js/jquery.placeholder.js 2012-01-16 18:18:24 +0000
@@ -0,0 +1,106 @@
1/*
2* Placeholder plugin for jQuery
3* ---
4* Copyright 2010, Daniel Stocks (http://webcloud.se)
5* Released under the MIT, BSD, and GPL Licenses.
6*/
7(function($) {
8 function Placeholder(input) {
9 this.input = input;
10 if (input.attr('type') == 'password') {
11 this.handlePassword();
12 }
13 // Prevent placeholder values from submitting
14 $(input[0].form).submit(function() {
15 if (input.hasClass('placeholder') && input[0].value == input.attr('placeholder')) {
16 input[0].value = '';
17 }
18 });
19 }
20 Placeholder.prototype = {
21 show : function(loading) {
22 // FF and IE saves values when you refresh the page. If the user refreshes the page with
23 // the placeholders showing they will be the default values and the input fields won't be empty.
24 if (this.input[0].value === '' || (loading && this.valueIsPlaceholder())) {
25 if (this.isPassword) {
26 try {
27 this.input[0].setAttribute('type', 'text');
28 } catch (e) {
29 this.input.before(this.fakePassword.show()).hide();
30 }
31 }
32 this.input.addClass('placeholder');
33 this.input[0].value = this.input.attr('placeholder');
34 }
35 },
36 hide : function() {
37 if (this.valueIsPlaceholder() && this.input.hasClass('placeholder')) {
38 this.input.removeClass('placeholder');
39 this.input[0].value = '';
40 if (this.isPassword) {
41 try {
42 this.input[0].setAttribute('type', 'password');
43 } catch (e) { }
44 // Restore focus for Opera and IE
45 this.input.show();
46 this.input[0].focus();
47 }
48 }
49 },
50 valueIsPlaceholder : function() {
51 return this.input[0].value == this.input.attr('placeholder');
52 },
53 handlePassword: function() {
54 var input = this.input;
55 input.attr('realType', 'password');
56 this.isPassword = true;
57 // IE < 9 doesn't allow changing the type of password inputs
58 if ($.browser.msie && input[0].outerHTML) {
59 var fakeHTML = $(input[0].outerHTML.replace(/type=(['"])?password\1/gi, 'type=$1text$1'));
60 this.fakePassword = fakeHTML.val(input.attr('placeholder')).addClass('placeholder').focus(function() {
61 input.trigger('focus');
62 $(this).hide();
63 });
64 $(input[0].form).submit(function() {
65 fakeHTML.remove();
66 input.show()
67 });
68 }
69 }
70 };
71 var NATIVE_SUPPORT = !!("placeholder" in document.createElement( "input" ));
72 $.fn.placeholder = function() {
73 return NATIVE_SUPPORT ? this : this.each(function() {
74 var input = $(this);
75 var placeholder = new Placeholder(input);
76 placeholder.show(true);
77 input.focus(function() {
78 placeholder.hide();
79 });
80 input.blur(function() {
81 placeholder.show(false);
82 });
83
84 // On page refresh, IE doesn't re-populate user input
85 // until the window.onload event is fired.
86 if ($.browser.msie) {
87 $(window).load(function() {
88 if(input.val()) {
89 input.removeClass("placeholder");
90 }
91 placeholder.show(true);
92 });
93 // What's even worse, the text cursor disappears
94 // when tabbing between text inputs, here's a fix
95 input.focus(function() {
96 if(this.value == "") {
97 var range = this.createTextRange();
98 range.collapse(true);
99 range.moveStart('character', 0);
100 range.select();
101 }
102 });
103 }
104 });
105 }
106})(jQuery);
0107
=== modified file 'lib/offspring/web/queuemanager/admin.py'
--- lib/offspring/web/queuemanager/admin.py 2011-09-26 20:02:27 +0000
+++ lib/offspring/web/queuemanager/admin.py 2012-01-16 18:18:24 +0000
@@ -3,7 +3,7 @@
33
4from django.contrib import admin4from django.contrib import admin
55
6from offspring.web.queuemanager.forms import ProjectBaseForm6from offspring.web.queuemanager.forms import ProjectSSHCredForm
7from offspring.web.queuemanager.models import (7from offspring.web.queuemanager.models import (
8 BuildRequest,8 BuildRequest,
9 BuildResult,9 BuildResult,
@@ -43,12 +43,14 @@
4343
4444
45class ProjectAdmin(admin.ModelAdmin):45class ProjectAdmin(admin.ModelAdmin):
46 fields = ['title', 'name', 'arch', 'project_group', 'launchpad_project', 'suite', 'series', 'priority', 'status', 'is_active', 'config_url', 'notes']46 fields = ['title', 'name', 'arch', 'project_group', 'launchpad_project',
47 'suite', 'series', 'priority', 'status', 'is_active',
48 'config_url', 'lp_user', 'lp_ssh_key', 'notes']
47 list_display = ('display_name', 'arch', 'series', 'project_group', 'launchpad_project', 'is_active', 'status', 'priority', 'config_url')49 list_display = ('display_name', 'arch', 'series', 'project_group', 'launchpad_project', 'is_active', 'status', 'priority', 'config_url')
48 list_filter = ['arch', 'series', 'is_active', 'project_group', 'status']50 list_filter = ['arch', 'series', 'is_active', 'project_group', 'status']
49 search_fields = ['title', 'name', 'arch', 'notes']51 search_fields = ['title', 'name', 'arch', 'notes']
50 actions = ['raise_priority', 'drop_priority', 'make_disabled', 'make_enabled']52 actions = ['raise_priority', 'drop_priority', 'make_disabled', 'make_enabled']
51 form = ProjectBaseForm53 form = ProjectSSHCredForm
5254
53 def raise_priority(self, request, queryset):55 def raise_priority(self, request, queryset):
54 for project in queryset:56 for project in queryset:
5557
=== modified file 'lib/offspring/web/queuemanager/forms.py'
--- lib/offspring/web/queuemanager/forms.py 2012-01-11 18:26:45 +0000
+++ lib/offspring/web/queuemanager/forms.py 2012-01-16 18:18:24 +0000
@@ -3,8 +3,8 @@
33
4from django.contrib.auth.models import User4from django.contrib.auth.models import User
5from django.forms import (5from django.forms import (
6 Form, ModelChoiceField, ModelForm, Textarea, TextInput, ValidationError)6 Form, ModelChoiceField, ModelForm, Select, Textarea, TextInput,
7from django.forms import fields7 ValidationError)
88
9from offspring.web.queuemanager.models import (9from offspring.web.queuemanager.models import (
10 Project,10 Project,
@@ -14,9 +14,8 @@
1414
15from offspring.web.queuemanager.widgets import SelectWithAddNew15from offspring.web.queuemanager.widgets import SelectWithAddNew
1616
17
17class ProjectBaseForm(ModelForm):18class ProjectBaseForm(ModelForm):
18 status = fields.CharField(max_length=200,
19 widget=fields.Select(choices=Project.STATUS_CHOICES), required=True)
2019
21 class Meta:20 class Meta:
22 model = Project21 model = Project
@@ -24,7 +23,10 @@
24 'name' : TextInput(attrs={'style': 'text-transform: lowercase;'}),23 'name' : TextInput(attrs={'style': 'text-transform: lowercase;'}),
25 'series' : TextInput(attrs={'style': 'text-transform: lowercase;'}),24 'series' : TextInput(attrs={'style': 'text-transform: lowercase;'}),
26 'config_url': TextInput(attrs={'size': 50}),25 'config_url': TextInput(attrs={'size': 50}),
27 'notes' : Textarea(attrs={'cols': 73, 'rows' : 4}),26 'notes' : Textarea(attrs={'cols': 70, 'rows' : 4}),
27 'status': Select(choices=Project.STATUS_CHOICES),
28 'lp_user' : TextInput(attrs={'size': 50}),
29 'lp_ssh_key' : Textarea(attrs={'cols': 70, 'rows' : 4}),
28 }30 }
2931
30 def clean_name(self):32 def clean_name(self):
@@ -50,11 +52,38 @@
50 exclude = ("priority", "is_active", "suite", "access_groups")52 exclude = ("priority", "is_active", "suite", "access_groups")
5153
5254
55class ProjectSSHCredForm(ProjectBaseForm):
56 lp_ssh_set = False
57 lp_ssh_set_message = (
58 "An SSH key is stored for this project. To replace it, paste a new "
59 "SSH private key block here. Leave this empty if you do not want to "
60 "change the existing key.")
61 lp_ssh_clear_meessage = (
62 "To enable access to private repositories, enter an SSH private key "
63 "here in the form of an ASCII key block.")
64
65 def clean_lp_ssh_key(self):
66 # The text entry for lp_ssh_key will never contain the existing value,
67 # so when it's not present in the request it's because the user didn't
68 # change it, so we just return the existing value for the rest of the
69 # validation to proceed normally.
70 new_key = self.cleaned_data["lp_ssh_key"]
71 if not new_key:
72 return self.instance.lp_ssh_key
73 return self.cleaned_data["lp_ssh_key"]
74
75
53class EditProjectForm(ProjectBaseForm):76class EditProjectForm(ProjectBaseForm):
54 launchpad_project = ModelChoiceField(77 launchpad_project = ModelChoiceField(
55 LaunchpadProject.objects, widget=SelectWithAddNew, required=False)78 LaunchpadProject.objects, widget=SelectWithAddNew, required=False)
56 class Meta(ProjectBaseForm.Meta):79 class Meta(ProjectBaseForm.Meta):
57 exclude = ("name", "priority", "is_active", "access_groups")80 exclude = ("name", "priority", "is_active", "access_groups",
81 "lp_user", "lp_ssh_key")
82
83
84class EditProjectSSHCredentialsForm(ProjectSSHCredForm):
85 class Meta(ProjectSSHCredForm.Meta):
86 fields = ("lp_user", "lp_ssh_key")
5887
5988
60class AccessGroupMemberForm(Form):89class AccessGroupMemberForm(Form):
6190
=== modified file 'lib/offspring/web/queuemanager/models.py'
--- lib/offspring/web/queuemanager/models.py 2012-01-16 17:28:38 +0000
+++ lib/offspring/web/queuemanager/models.py 2012-01-16 18:18:24 +0000
@@ -2,9 +2,11 @@
2# GNU Affero General Public License version 3 (see the file LICENSE).2# GNU Affero General Public License version 3 (see the file LICENSE).
33
4import math4import math
5import re
5from datetime import date, datetime, timedelta6from datetime import date, datetime, timedelta
67
7from django.contrib.auth.models import AnonymousUser, User8from django.contrib.auth.models import AnonymousUser, User
9from django.core.exceptions import ValidationError
8from django.db import (10from django.db import (
9 connection,11 connection,
10 models12 models
@@ -198,6 +200,25 @@
198 return is_visible_to(user)200 return is_visible_to(user)
199201
200202
203def validate_lp_ssh_key(value):
204 """Check to see if the value looks like an SSH private key"""
205 if not value:
206 return
207
208 key_search_regexp = (r"-----BEGIN \w+ PRIVATE KEY-----"
209 r".*"
210 r"-----END \w+ PRIVATE KEY-----")
211 is_key = re.search(key_search_regexp, value, re.DOTALL | re.MULTILINE)
212
213 if not is_key:
214 msg = ("The key you entered doesn't appear to be valid. I am "
215 "expecting a key in the form:\n "
216 "-----BEGIN <type> PRIVATE KEY-----\n"
217 "<ASCII key>\n"
218 "-----END <type> PRIVATE KEY-----\n")
219 raise ValidationError(msg)
220
221
201class Project(AccessGroupMixin, MaybePrivateMixin):222class Project(AccessGroupMixin, MaybePrivateMixin):
202 #XXX: This should probably be managed in the database.223 #XXX: This should probably be managed in the database.
203 STATUS_CHOICES = (224 STATUS_CHOICES = (
@@ -233,10 +254,12 @@
233 # The Launchpad User and SSH key are stored per project. If we stored them254 # The Launchpad User and SSH key are stored per project. If we stored them
234 # per LaunchpadProject, anyone who could create a Project referencing that255 # per LaunchpadProject, anyone who could create a Project referencing that
235 # LaunchpadProject could get access to the private data in it.256 # LaunchpadProject could get access to the private data in it.
236 lp_user = models.TextField("Launchpad User", null=True, editable=False,257 lp_user = models.TextField("Launchpad User", null=True, blank=True)
237 blank=True)258 lp_ssh_key = models.TextField(
238 lp_ssh_key = models.TextField("Launchpad User's SSH Key", blank=True,259 "Launchpad User's SSH Key", blank=True, null=True, editable=True,
239 null=True, editable=False)260 validators=[validate_lp_ssh_key],
261 help_text=("Enter a private SSH ASCII key block, complete with begin "
262 "and end markers."))
240263
241 notes = models.TextField(blank=True, null=True)264 notes = models.TextField(blank=True, null=True)
242265
@@ -249,6 +272,17 @@
249 def __unicode__(self):272 def __unicode__(self):
250 return self.display_name()273 return self.display_name()
251274
275 def clean(self):
276 if self.lp_user and not self.lp_ssh_key:
277 raise ValidationError(
278 "You must specify the SSH key for the given Launchpad user")
279 elif self.lp_ssh_key and not self.lp_user:
280 raise ValidationError(
281 "You must specify the Launchpad user for the given SSH key")
282 else:
283 # All good, move on.
284 pass
285
252 def _is_visible_to(self, user):286 def _is_visible_to(self, user):
253 if not self._is_private or user == self.owner:287 if not self._is_private or user == self.owner:
254 return True288 return True
255289
=== modified file 'lib/offspring/web/queuemanager/tests/test_views.py'
--- lib/offspring/web/queuemanager/tests/test_views.py 2012-01-13 15:07:07 +0000
+++ lib/offspring/web/queuemanager/tests/test_views.py 2012-01-16 18:18:24 +0000
@@ -179,6 +179,115 @@
179 status_code=200, msg_prefix=response.content)179 status_code=200, msg_prefix=response.content)
180180
181181
182class ProjectEditSSHViewTests(TestCase):
183 def test_edit_ssh_auth(self):
184 project = factory.make_project(is_private=True)
185 user = project.owner
186 grant_permission_to_user(user, "change_project")
187 self.assertTrue(
188 self.client.login(username=user.username, password=user.username))
189
190 key = ("-----BEGIN type PRIVATE KEY-----\n"
191 "12qwaszx34erdfcv\n"
192 "-----END type PRIVATE KEY-----\n")
193
194 data = {"lp_user": "lpuser",
195 "lp_ssh_key": key,
196 "_save": 1}
197 response = self.client.post(
198 reverse(
199 "offspring.web.queuemanager.views.project_editsshcredentials",
200 args=[project.name]),
201 data, follow=True)
202
203 self.assertNotContains(
204 response, "form-errors", status_code=200,
205 msg_prefix=response.content)
206
207 # Re-load the project from the database to pick up changes
208 user_visible_objects = Project.all_objects.accessible_by_user(user)
209 project = get_possibly_private_object(
210 user, user_visible_objects, pk=project.name)
211
212 self.assertEqual("lpuser", project.lp_user)
213 self.assertEqual(key, project.lp_ssh_key)
214
215 def test_edit_ssh_auth_bad_key(self):
216 project = factory.make_project(is_private=True)
217 user = project.owner
218 grant_permission_to_user(user, "change_project")
219 self.assertTrue(
220 self.client.login(username=user.username, password=user.username))
221
222 key = ("-----BEGIN type PR---VATE KEY-----\n"
223 "12qwaszx34erdfcv\n"
224 "-----END type PRIVATE KEY-----\n")
225
226 data = {"lp_user": "lpuser",
227 "lp_ssh_key": key,
228 '_save': 1}
229 response = self.client.post(
230 reverse(
231 "offspring.web.queuemanager.views.project_editsshcredentials",
232 args=[project.name]),
233 data, follow=True)
234
235 self.assertEqual(200, response.status_code)
236
237 # re-load the project from the database to pick up changes
238 user_visible_objects = Project.all_objects.accessible_by_user(user)
239 project = get_possibly_private_object(
240 user, user_visible_objects, pk=project.name)
241
242 self.assertNotEqual("lpuser", project.lp_user)
243 self.assertNotEqual(key, project.lp_ssh_key)
244
245 def test_edit_ssh_auth_private_project(self):
246 project = factory.make_project(is_private=True)
247 user = factory.make_user()
248 self.assertTrue(
249 self.client.login(username=user.username, password=user.username))
250
251 # Check can't read a private projects ssh cred page
252 response = self.client.get(
253 reverse(
254 "offspring.web.queuemanager.views.project_editsshcredentials",
255 args=[project.name]),
256 follow=True)
257 self.assertEqual(200, response.status_code)
258 self.assertContains(response, "Lexbuilder Control Administration")
259 self.assertContains(response, "Username")
260 self.assertContains(response, "Password")
261
262 # Check can't write to a private projects ssh cred page
263 key = ("-----BEGIN type PR---VATE KEY-----\n"
264 "12qwaszx34erdfcv\n"
265 "-----END type PRIVATE KEY-----\n")
266
267 data = {"lp_user": "lpuser",
268 "lp_ssh_key": key,
269 "_save": 1}
270 response = self.client.post(
271 reverse(
272 "offspring.web.queuemanager.views.project_editsshcredentials",
273 args=[project.name]),
274 data, follow=True)
275
276 self.assertEqual(200, response.status_code)
277 self.assertContains(response, "Lexbuilder Control Administration")
278 self.assertContains(response, "Username")
279 self.assertContains(response, "Password")
280
281 # re-load the project from the database to pick up changes
282 user_visible_objects = Project.all_objects.accessible_by_user(
283 project.owner)
284 project = get_possibly_private_object(
285 project.owner, user_visible_objects, pk=project.name)
286
287 self.assertNotEqual("lpuser", project.lp_user)
288 self.assertNotEqual(key, project.lp_ssh_key)
289
290
182class ProjectCreateViewTests(TestCase):291class ProjectCreateViewTests(TestCase):
183 view_path = 'offspring.web.queuemanager.views.project_create'292 view_path = 'offspring.web.queuemanager.views.project_create'
184293
@@ -199,7 +308,9 @@
199 "_is_private": [u"on"],308 "_is_private": [u"on"],
200 "_save": [u"Save"],309 "_save": [u"Save"],
201 "series": [u"natty"],310 "series": [u"natty"],
202 "arch": [u"amd64"]}311 "arch": [u"amd64"],
312 "lp_user": "",
313 "lp_ssh_key": ""}
203 response = self.client.post(reverse(self.view_path), data)314 response = self.client.post(reverse(self.view_path), data)
204 self.assertContains(315 self.assertContains(
205 response, "A private project needs an owner",316 response, "A private project needs an owner",
206317
=== modified file 'lib/offspring/web/queuemanager/views.py'
--- lib/offspring/web/queuemanager/views.py 2012-01-11 16:46:24 +0000
+++ lib/offspring/web/queuemanager/views.py 2012-01-16 18:18:24 +0000
@@ -42,6 +42,7 @@
42 AccessGroupMemberForm,42 AccessGroupMemberForm,
43 CreateProjectForm,43 CreateProjectForm,
44 EditProjectForm,44 EditProjectForm,
45 EditProjectSSHCredentialsForm,
45 LaunchpadProjectForm,46 LaunchpadProjectForm,
46 ReleaseForm,47 ReleaseForm,
47)48)
@@ -359,8 +360,8 @@
359 else:360 else:
360 form = AccessGroupMemberForm()361 form = AccessGroupMemberForm()
361 pageData = {362 pageData = {
362 "csrf_token" : csrf.get_token(request),363 "csrf_token": csrf.get_token(request),
363 "project" : project,364 "project": project,
364 "form": form,365 "form": form,
365 "allowed_users": allowed_users,366 "allowed_users": allowed_users,
366 }367 }
@@ -369,6 +370,49 @@
369 context_instance=RequestContext(request))370 context_instance=RequestContext(request))
370371
371372
373@permission_required("queuemanager.change_project")
374def project_editsshcredentials(request, projectName):
375 user_visible_objects = Project.all_objects.accessible_by_user(
376 request.user)
377 project = get_possibly_private_object(
378 request.user, user_visible_objects, pk=projectName)
379 access_group = None
380 allowed_users = []
381 if len(project.access_groups.all()) > 0:
382 # We always use the first access group because there's no way for
383 # users to register more than one access group for any given project.
384 access_group = project.access_groups.all()[0]
385 allowed_users = access_group.members.all()
386
387 if request.method == "POST":
388
389 form = EditProjectSSHCredentialsForm(request.POST, instance=project)
390
391 # Don't validate the data (call form.is_valid) before deleting - we
392 # don't care what the form input is since we don't use it.
393 if "delete" in request.POST:
394 project.lp_user = ""
395 project.lp_ssh_key = ""
396 project.save()
397 return HttpResponseRedirect(".")
398
399 if form.is_valid():
400 if "_save" in request.POST:
401 form.save()
402 return HttpResponseRedirect(".")
403
404 else:
405 form = EditProjectSSHCredentialsForm(instance=project)
406 pageData = {
407 "csrf_token": csrf.get_token(request),
408 "project": project,
409 "form": form,
410 "allowed_users": allowed_users}
411 return render_to_response(
412 "queuemanager/project_edit_credentials.html", pageData,
413 context_instance=RequestContext(request))
414
415
372def projectgroup_details(request, projectGroupName):416def projectgroup_details(request, projectGroupName):
373 pg = get_object_or_404(417 pg = get_object_or_404(
374 ProjectGroup, pk=projectGroupName)418 ProjectGroup, pk=projectGroupName)
375419
=== modified file 'lib/offspring/web/templates/queuemanager/project_create.html'
--- lib/offspring/web/templates/queuemanager/project_create.html 2011-02-28 22:48:46 +0000
+++ lib/offspring/web/templates/queuemanager/project_create.html 2012-01-16 18:18:24 +0000
@@ -5,7 +5,9 @@
5{% endblock %}5{% endblock %}
66
7{% block header_js %}7{% block header_js %}
8<script type="text/javascript" src="/media/js/admin/RelatedObjectLookups.js"></script> 8<script type="text/javascript" src="/media/js/admin/RelatedObjectLookups.js"></script>
9<script type="text/javascript" src="/media/js/jquery.min.js"></script>
10<script type="text/javascript" src="/assets/js/jquery.placeholder.js"></script>
9<script type="text/javascript">11<script type="text/javascript">
10/* Override showAddAnotherPopup to use custom height and width. */12/* Override showAddAnotherPopup to use custom height and width. */
11function showAddAnotherPopup(triggeringLink) {13function showAddAnotherPopup(triggeringLink) {
@@ -29,29 +31,67 @@
29{% endblock %}31{% endblock %}
3032
31{% block content %}33{% block content %}
32 <form method="POST" action="">{% csrf_token %}34 {{ form.non_field_errors }}
35 <form method="POST" action="" name="projsettings">{% csrf_token %}
33 <div class="module aligned ">36 <div class="module aligned ">
34 {% for field in form %}37 {% for field in form %}
35 <div class="form-row {% if line.errors %} errors{% endif %} {{ field.name }}">38 {% if field.html_name == "lp_user" or field.html_name == "lp_ssh_key" %}
36 <div{% if not line.fields|length_is:"1" %} class="field-box"{% endif %}>39 <!-- This catches lp_user and lp_ssh_key so they aren't shown in the top of
37 {% if field.is_checkbox %}40 the page. They are displayed below in their own section after some help text -->
38 {{ field }}{{ field.label_tag }}41 {% else %}
39 {% else %}42 <div class="form-row {% if line.errors %} errors{% endif %} {{ field.name }}">
40 {{ field.label_tag }}43 <div{% if not line.fields|length_is:"1" %} class="field-box"{% endif %}>
41 {% if field.is_readonly %}44 {% if field.is_checkbox %}
42 <p>{{ field.contents }}</p>45 {{ field }}{{ field.label_tag }}
43 {% else %}46 {% else %}
44 {{ field }}47 {{ field.label_tag }}
45 {% endif %}48 {% if field.is_readonly %}
46 {% endif %}49 <p>{{ field.contents }}</p>
47 {% if field.field.field.help_text %}50 {% else %}
48 <p class="help">{{ field.field.field.help_text|safe }}</p>51 {{ field }}
49 {% endif %}52 {% endif %}
53 {% endif %}
54 {% if field.field.field.help_text %}
55 <p class="help">{{ field.field.field.help_text|safe }}</p>
56 {% endif %}
57 </div>
58 {{ field.errors }}
50 </div>59 </div>
51 {{ field.errors }}60 {% endif %}
52 </div>
53 61
54 {% endfor %}62 {% endfor %}
63
64 <br>
65 If the config URL above points to a Bazaar repository that is only
66 available to authenticated users, you should add a user and private SSH
67 key to give the builder access. The SSH key will never be revealed. It
68 can be changed or deleted later. It should not be passphrase protected.
69 If you would like to add these details, tick here:
70
71 <br>
72
73 <div class="form-row {{ form.lp_user.name }}">
74 <div class="field-box">
75 {{ form.lp_user.label_tag }}
76 {{ form.lp_user }}
77 </div>
78 {{ form.lp_user.errors }}
79 </div>
80
81 <div class="form-row {{ form.lp_ssh_key.name }}">
82 <div class="field-box">
83 {{ form.lp_ssh_key.label_tag }}
84 <br>
85 {{ form.lp_ssh_key }}
86 {% if form.lp_ssh_key.help_text %}
87 <p class="help">{{ form.lp_ssh_key.help_text|safe }}</p>
88 {% endif %}
89 </div>
90 {{ form.lp_ssh_key.errors|linebreaksbr }}
91 </div>
92
93 <br>
94
55 <div class="submit-row" style="overflow: auto;">95 <div class="submit-row" style="overflow: auto;">
56 <input type="submit" value="Save" class="default" name="_save"/>96 <input type="submit" value="Save" class="default" name="_save"/>
57 <input type="button" value="Cancel" class="default" name="_cancel" OnClick="window.location.href = '/';"/>97 <input type="button" value="Cancel" class="default" name="_cancel" OnClick="window.location.href = '/';"/>
5898
=== modified file 'lib/offspring/web/templates/queuemanager/project_edit.html'
--- lib/offspring/web/templates/queuemanager/project_edit.html 2011-03-03 01:50:40 +0000
+++ lib/offspring/web/templates/queuemanager/project_edit.html 2012-01-16 18:18:24 +0000
@@ -44,14 +44,28 @@
44 {{ field }}44 {{ field }}
45 {% endif %}45 {% endif %}
46 {% endif %}46 {% endif %}
47 {% if field.field.field.help_text %}47 {% if field.help_text %}
48 <p class="help">{{ field.field.field.help_text|safe }}</p>48 <p class="help">{{ field.help_text|safe }}</p>
49 {% endif %}49 {% endif %}
50 </div>50 </div>
51 {{ field.errors }}51 {{ field.errors }}
52 </div>52 </div>
53 53
54 {% endfor %}54 {% endfor %}
55
56 <br>
57 If the config URL above points to a Bazaar repository that is only
58 available to authenticated users, you should add a user and private SSH
59 key to give the builder access. The SSH key will never be revealed. It
60 can be changed or deleted later. It should not be passphrase protected.
61
62 {% if form.instance.lp_ssh_key %}
63 To update or delete these details, use
64 {% else %}
65 If you would like to add these details,
66 {% endif %}
67 <a href="+editcredentials">use this form.</a>
68
55 <div class="submit-row" style="overflow: auto;">69 <div class="submit-row" style="overflow: auto;">
56 <input type="submit" value="Save" class="default" name="_save"/>70 <input type="submit" value="Save" class="default" name="_save"/>
57 <input type="button" value="Cancel" class="default" name="_cancel" OnClick="window.location.href = '{% url offspring.web.queuemanager.views.project_details project.name %}';"/>71 <input type="button" value="Cancel" class="default" name="_cancel" OnClick="window.location.href = '{% url offspring.web.queuemanager.views.project_details project.name %}';"/>
5872
=== added file 'lib/offspring/web/templates/queuemanager/project_edit_credentials.html'
--- lib/offspring/web/templates/queuemanager/project_edit_credentials.html 1970-01-01 00:00:00 +0000
+++ lib/offspring/web/templates/queuemanager/project_edit_credentials.html 2012-01-16 18:18:24 +0000
@@ -0,0 +1,70 @@
1{% extends "base.html" %}
2
3{% block header_css %}
4<link rel="stylesheet" type="text/css" href="/media/css/forms.css" />
5{% endblock %}
6
7{% block header_js %}
8<script type="text/javascript" src="/media/js/jquery.min.js"></script>
9<script type="text/javascript" src="/assets/js/jquery.placeholder.js"></script>
10{% endblock %}
11
12{% block title %}
13Update SSH Credentials for {{project.title|capfirst}}
14{% endblock %}
15
16{% block content %}
17 <div id="form-errors">{{ form.non_field_errors }}</div>
18 <form method="POST" action="" name="projsettings">{% csrf_token %}
19 <div class="module aligned ">
20 <div class="form-row {{ form.lp_user.name }}">
21 <div class="field-box">
22 {{ form.lp_user.label_tag }}
23 {{ form.lp_user }}
24 </div>
25 {{ form.lp_user.errors }}
26 </div>
27
28 <div class="form-row {{ form.lp_ssh_key.name }}">
29 <div class="field-box">
30 {{ form.lp_ssh_key.label_tag }}
31 <br>
32 {% if form.errors and form.data.lp_ssh_key %}
33 <!-- The user submitted a non-empty SSH key and there's
34 a form error, so we show the submitted key to help
35 them figure out what is wrong. -->
36 {{ form.lp_ssh_key }}
37 {% else %}
38 <textarea id="id_lp_ssh_key" rows="4" cols="70" name="lp_ssh_key"
39 placeholder="{% if form.instance.lp_ssh_key %}{{ form.lp_ssh_set_message }}{% else %}{{ form.lp_ssh_clear_meessage }}{% endif %}"></textarea>
40 {% endif %}
41 {% if form.lp_ssh_key.help_text %}
42 <p class="help">
43 {{ form.lp_ssh_key.help_text|safe }}
44 {% if form.instance.lp_ssh_key %}
45 Your saved key is not shown. To replace it, paste a new key in the above box.
46 {% else %}
47 No key is stored for this project. Saved keys are not shown.
48 {% endif %}
49 </p>
50 {% endif %}
51 </div>
52 {{ form.lp_ssh_key.errors|linebreaksbr }}
53 </div>
54
55 <br>
56
57 <div class="submit-row" style="overflow: auto;">
58 <input type="submit" value="Save Updated Credentials" class="default" name="_save"/>
59 <input type="submit" value="Delete Existing Credentials" class="default" name="delete"/>
60 <input type="button" value="Cancel" class="default" name="_cancel" OnClick="window.location.href = '{% url offspring.web.queuemanager.views.project_details project.name %}';"/>
61 </div>
62 </div>
63 </form>
64{% endblock %}
65
66{% block two-columns %}
67<script>
68 $('input[placeholder], textarea[placeholder]').placeholder();
69</script>
70{% endblock %}
071
=== modified file 'lib/offspring/web/urls.py'
--- lib/offspring/web/urls.py 2011-12-15 13:58:03 +0000
+++ lib/offspring/web/urls.py 2012-01-16 18:18:24 +0000
@@ -75,6 +75,8 @@
75 (r'^projects/(?P<projectName>[^/]+)/\+build$', 'queue_build'),75 (r'^projects/(?P<projectName>[^/]+)/\+build$', 'queue_build'),
76 (r'^projects/(?P<projectName>[^/]+)/\+builds$', 'builds'),76 (r'^projects/(?P<projectName>[^/]+)/\+builds$', 'builds'),
77 (r'^projects/(?P<projectName>[^/]+)/$', 'project_details'),77 (r'^projects/(?P<projectName>[^/]+)/$', 'project_details'),
78 (r'^projects/(?P<projectName>[^/]+)/\+editcredentials$',
79 'project_editsshcredentials'),
78 (r'^projects/$', 'projects'),80 (r'^projects/$', 'projects'),
79 (r'^schedule/\+api/milestones/$', milestone_handler),81 (r'^schedule/\+api/milestones/$', milestone_handler),
80 (r'^schedule/\+(?P<milestoneType>[^/]+)-milestones$', 'milestones'),82 (r'^schedule/\+(?P<milestoneType>[^/]+)-milestones$', 'milestones'),

Subscribers

People subscribed via source and target branches