diff mbox

[Branch,~linaro-validation/lava-server/trunk] Rev 238: Merge lava-server changes for late 2011-09 relase.

Message ID 20110926090115.326.41829.launchpad@ackee.canonical.com
State Accepted
Headers show

Commit Message

Zygmunt Krynicki Sept. 26, 2011, 9:01 a.m. UTC
Merge authors:
  Zygmunt Krynicki (zkrynicki)
Related merge proposals:
  https://code.launchpad.net/~zkrynicki/lava-server/renaming-projects/+merge/76868
  proposed by: Zygmunt Krynicki (zkrynicki)
  review: Approve - Paul Larson (pwlars)
  https://code.launchpad.net/~zkrynicki/lava-server/enable-lava-projects/+merge/76703
  proposed by: Zygmunt Krynicki (zkrynicki)
  review: Approve - Paul Larson (pwlars)
  https://code.launchpad.net/~zkrynicki/lava-server/lava-projects-app/+merge/76702
  proposed by: Zygmunt Krynicki (zkrynicki)
  review: Approve - Paul Larson (pwlars)
  https://code.launchpad.net/~zkrynicki/lava-server/enable-lava-markitup/+merge/76700
  proposed by: Zygmunt Krynicki (zkrynicki)
  review: Approve - Paul Larson (pwlars)
  https://code.launchpad.net/~zkrynicki/lava-server/tabular-form-style/+merge/76699
  proposed by: Zygmunt Krynicki (zkrynicki)
  review: Approve - Paul Larson (pwlars)
  https://code.launchpad.net/~zkrynicki/lava-server/jquery-slugify/+merge/76698
  proposed by: Zygmunt Krynicki (zkrynicki)
  review: Approve - Paul Larson (pwlars)
  https://code.launchpad.net/~zkrynicki/lava-server/lava-markitup/+merge/76692
  proposed by: Zygmunt Krynicki (zkrynicki)
  review: Approve - Paul Larson (pwlars)
  https://code.launchpad.net/~zkrynicki/lava-server/new-sidebar/+merge/76564
  proposed by: Zygmunt Krynicki (zkrynicki)
  https://code.launchpad.net/~zkrynicki/lava-server/update-aristo-theme/+merge/75697
  proposed by: Zygmunt Krynicki (zkrynicki)
  review: Approve - Paul Larson (pwlars)
  https://code.launchpad.net/~zkrynicki/lava-server/use-sidebar-in-xmlrpc-views/+merge/75694
  proposed by: Zygmunt Krynicki (zkrynicki)
  review: Approve - Paul Larson (pwlars)
  https://code.launchpad.net/~zkrynicki/lava-server/simple-testing-and-running/+merge/75693
  proposed by: Zygmunt Krynicki (zkrynicki)
  review: Approve - Paul Larson (pwlars)
------------------------------------------------------------
revno: 238 [merge]
committer: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
branch nick: merge
timestamp: Mon 2011-09-26 10:53:50 +0200
message:
  Merge lava-server changes for late 2011-09 relase.
  
  This adds the following high-level elements:
   * A new look and feel of the "lava server sidebar" which was never a sidebar
     before. The new sidebar can be dynamically hidden and restored, remembers
     user preferences and also holds the sign-in/sign-out controls.
   * Minor theme updates with the new release of Aristo jQuery UI theme and some
     changes to the way LAVA presents some forms.
   * Support for creating projects in LAVA (with the Project model and
     ProjectFormerIdentifier helper model for renaming)
   * A common markitup application for rich text editing with a simple theme and
     support for markdown formatting
   * A jQuery.slugify plugin for creating slug-like text from free-form text
   * A jQuery.cookie plugin for interacting with browser cookies 
added:
  lava_markitup/
  lava_markitup/__init__.py
  lava_markitup/models.py
  lava_markitup/static/
  lava_markitup/static/lava_markitup/
  lava_markitup/static/lava_markitup/jquery.markitup.js
  lava_markitup/static/lava_markitup/preview.css
  lava_markitup/static/lava_markitup/sets/
  lava_markitup/static/lava_markitup/sets/markdown/
  lava_markitup/static/lava_markitup/sets/markdown/images/
  lava_markitup/static/lava_markitup/sets/markdown/images/bold.png
  lava_markitup/static/lava_markitup/sets/markdown/images/code.png
  lava_markitup/static/lava_markitup/sets/markdown/images/h1.png
  lava_markitup/static/lava_markitup/sets/markdown/images/h2.png
  lava_markitup/static/lava_markitup/sets/markdown/images/h3.png
  lava_markitup/static/lava_markitup/sets/markdown/images/h4.png
  lava_markitup/static/lava_markitup/sets/markdown/images/h5.png
  lava_markitup/static/lava_markitup/sets/markdown/images/h6.png
  lava_markitup/static/lava_markitup/sets/markdown/images/italic.png
  lava_markitup/static/lava_markitup/sets/markdown/images/link.png
  lava_markitup/static/lava_markitup/sets/markdown/images/list-bullet.png
  lava_markitup/static/lava_markitup/sets/markdown/images/list-numeric.png
  lava_markitup/static/lava_markitup/sets/markdown/images/picture.png
  lava_markitup/static/lava_markitup/sets/markdown/images/preview.png
  lava_markitup/static/lava_markitup/sets/markdown/images/quotes.png
  lava_markitup/static/lava_markitup/sets/markdown/set.js
  lava_markitup/static/lava_markitup/sets/markdown/style.css
  lava_markitup/static/lava_markitup/skins/
  lava_markitup/static/lava_markitup/skins/simple/
  lava_markitup/static/lava_markitup/skins/simple/images/
  lava_markitup/static/lava_markitup/skins/simple/images/handle.png
  lava_markitup/static/lava_markitup/skins/simple/images/menu.png
  lava_markitup/static/lava_markitup/skins/simple/images/submenu.png
  lava_markitup/static/lava_markitup/skins/simple/style.css
  lava_markitup/static/lava_markitup/templates/
  lava_markitup/static/lava_markitup/templates/preview.css
  lava_markitup/static/lava_markitup/templates/preview.html
  lava_markitup/templates/
  lava_markitup/templates/lava_markitup/
  lava_markitup/templates/lava_markitup/preview.html
  lava_markitup/tests.py
  lava_markitup/urls.py
  lava_markitup/views.py
  lava_projects/
  lava_projects/__init__.py
  lava_projects/admin.py
  lava_projects/extension.py
  lava_projects/forms.py
  lava_projects/migrations/
  lava_projects/migrations/0001_add_model_Project.py
  lava_projects/migrations/0002_add_model_ProjectFormerIdentifier.py
  lava_projects/migrations/__init__.py
  lava_projects/models.py
  lava_projects/templates/
  lava_projects/templates/lava_projects/
  lava_projects/templates/lava_projects/_project_actions.html
  lava_projects/templates/lava_projects/_project_form.html
  lava_projects/templates/lava_projects/project_detail.html
  lava_projects/templates/lava_projects/project_list.html
  lava_projects/templates/lava_projects/project_register_form.html
  lava_projects/templates/lava_projects/project_rename_form.html
  lava_projects/templates/lava_projects/project_root.html
  lava_projects/templates/lava_projects/project_update_form.html
  lava_projects/urls.py
  lava_projects/views.py
  lava_server/htdocs/css/jquery.lava.sidebar.css
  lava_server/htdocs/js/jquery.cookie.js
  lava_server/htdocs/js/jquery.lava.sidebar.js
  lava_server/htdocs/js/jquery.slugify.js
modified:
  lava_server/htdocs/css/Aristo/jquery-ui-1.8.7.custom.css
  lava_server/htdocs/css/default.css
  lava_server/manage.py
  lava_server/settings/common.py
  lava_server/settings/development.py
  lava_server/templates/index.html
  lava_server/templates/layouts/base.html
  lava_server/templates/layouts/content_with_sidebar.html
  lava_server/templates/linaro_django_xmlrpc/_base.html
  lava_server/urls.py
  setup.py


--
lp:lava-server
https://code.launchpad.net/~linaro-validation/lava-server/trunk

You are subscribed to branch lp:lava-server.
To unsubscribe from this branch go to https://code.launchpad.net/~linaro-validation/lava-server/trunk/+edit-subscription
diff mbox

Patch

=== added directory 'lava_markitup'
=== added file 'lava_markitup/__init__.py'
=== added file 'lava_markitup/models.py'
=== added directory 'lava_markitup/static'
=== added directory 'lava_markitup/static/lava_markitup'
=== added file 'lava_markitup/static/lava_markitup/jquery.markitup.js'
--- lava_markitup/static/lava_markitup/jquery.markitup.js	1970-01-01 00:00:00 +0000
+++ lava_markitup/static/lava_markitup/jquery.markitup.js	2011-09-23 08:17:10 +0000
@@ -0,0 +1,593 @@ 
+// ----------------------------------------------------------------------------
+// markItUp! Universal MarkUp Engine, JQuery plugin
+// v 1.1.x
+// Dual licensed under the MIT and GPL licenses.
+// ----------------------------------------------------------------------------
+// Copyright (C) 2007-2011 Jay Salvat
+// http://markitup.jaysalvat.com/
+// ----------------------------------------------------------------------------
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+// 
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+// THE SOFTWARE.
+// ----------------------------------------------------------------------------
+(function($) {
+	$.fn.markItUp = function(settings, extraSettings) {
+		var options, ctrlKey, shiftKey, altKey;
+		ctrlKey = shiftKey = altKey = false;
+	
+		options = {	id:						'',
+					nameSpace:				'',
+					root:					'',
+					previewInWindow:		'', // 'width=800, height=600, resizable=yes, scrollbars=yes'
+					previewAutoRefresh:		true,
+					previewPosition:		'after',
+					previewTemplatePath:	'~/templates/preview.html',
+					previewParser:			false,
+					previewParserPath:		'',
+					previewParserVar:		'data',
+					resizeHandle:			true,
+					beforeInsert:			'',
+					afterInsert:			'',
+					onEnter:				{},
+					onShiftEnter:			{},
+					onCtrlEnter:			{},
+					onTab:					{},
+					markupSet:			[	{ /* set */ } ]
+				};
+		$.extend(options, settings, extraSettings);
+
+		// compute markItUp! path
+		if (!options.root) {
+			$('script').each(function(a, tag) {
+				miuScript = $(tag).get(0).src.match(/(.*)jquery\.markitup(\.pack)?\.js$/);
+				if (miuScript !== null) {
+					options.root = miuScript[1];
+				}
+			});
+		}
+
+		return this.each(function() {
+			var $$, textarea, levels, scrollPosition, caretPosition, caretOffset,
+				clicked, hash, header, footer, previewWindow, template, iFrame, abort;
+			$$ = $(this);
+			textarea = this;
+			levels = [];
+			abort = false;
+			scrollPosition = caretPosition = 0;
+			caretOffset = -1;
+
+			options.previewParserPath = localize(options.previewParserPath);
+			options.previewTemplatePath = localize(options.previewTemplatePath);
+
+			// apply the computed path to ~/
+			function localize(data, inText) {
+				if (inText) {
+					return 	data.replace(/("|')~\//g, "$1"+options.root);
+				}
+				return 	data.replace(/^~\//, options.root);
+			}
+
+			// init and build editor
+			function init() {
+				id = ''; nameSpace = '';
+				if (options.id) {
+					id = 'id="'+options.id+'"';
+				} else if ($$.attr("id")) {
+					id = 'id="markItUp'+($$.attr("id").substr(0, 1).toUpperCase())+($$.attr("id").substr(1))+'"';
+
+				}
+				if (options.nameSpace) {
+					nameSpace = 'class="'+options.nameSpace+'"';
+				}
+				$$.wrap('<div '+nameSpace+'></div>');
+				$$.wrap('<div '+id+' class="markItUp"></div>');
+				$$.wrap('<div class="markItUpContainer"></div>');
+				$$.addClass("markItUpEditor");
+
+				// add the header before the textarea
+				header = $('<div class="markItUpHeader"></div>').insertBefore($$);
+				$(dropMenus(options.markupSet)).appendTo(header);
+
+				// add the footer after the textarea
+				footer = $('<div class="markItUpFooter"></div>').insertAfter($$);
+
+				// add the resize handle after textarea
+				if (options.resizeHandle === true && $.browser.safari !== true) {
+					resizeHandle = $('<div class="markItUpResizeHandle"></div>')
+						.insertAfter($$)
+						.bind("mousedown", function(e) {
+							var h = $$.height(), y = e.clientY, mouseMove, mouseUp;
+							mouseMove = function(e) {
+								$$.css("height", Math.max(20, e.clientY+h-y)+"px");
+								return false;
+							};
+							mouseUp = function(e) {
+								$("html").unbind("mousemove", mouseMove).unbind("mouseup", mouseUp);
+								return false;
+							};
+							$("html").bind("mousemove", mouseMove).bind("mouseup", mouseUp);
+					});
+					footer.append(resizeHandle);
+				}
+
+				// listen key events
+				$$.keydown(keyPressed).keyup(keyPressed);
+				
+				// bind an event to catch external calls
+				$$.bind("insertion", function(e, settings) {
+					if (settings.target !== false) {
+						get();
+					}
+					if (textarea === $.markItUp.focused) {
+						markup(settings);
+					}
+				});
+
+				// remember the last focus
+				$$.focus(function() {
+					$.markItUp.focused = this;
+				});
+			}
+
+			// recursively build header with dropMenus from markupset
+			function dropMenus(markupSet) {
+				var ul = $('<ul></ul>'), i = 0;
+				$('li:hover > ul', ul).css('display', 'block');
+				$.each(markupSet, function() {
+					var button = this, t = '', title, li, j;
+					title = (button.key) ? (button.name||'')+' [Ctrl+'+button.key+']' : (button.name||'');
+					key   = (button.key) ? 'accesskey="'+button.key+'"' : '';
+					if (button.separator) {
+						li = $('<li class="markItUpSeparator">'+(button.separator||'')+'</li>').appendTo(ul);
+					} else {
+						i++;
+						for (j = levels.length -1; j >= 0; j--) {
+							t += levels[j]+"-";
+						}
+						li = $('<li class="markItUpButton markItUpButton'+t+(i)+' '+(button.className||'')+'"><a href="" '+key+' title="'+title+'">'+(button.name||'')+'</a></li>')
+						.bind("contextmenu", function() { // prevent contextmenu on mac and allow ctrl+click
+							return false;
+						}).click(function() {
+							return false;
+						}).bind("focusin", function(){
+                            $$.focus();
+						}).mouseup(function() {
+							if (button.call) {
+								eval(button.call)();
+							}
+							setTimeout(function() { markup(button) },1);
+							return false;
+						}).hover(function() {
+								$('> ul', this).show();
+								$(document).one('click', function() { // close dropmenu if click outside
+										$('ul ul', header).hide();
+									}
+								);
+							}, function() {
+								$('> ul', this).hide();
+							}
+						).appendTo(ul);
+						if (button.dropMenu) {
+							levels.push(i);
+							$(li).addClass('markItUpDropMenu').append(dropMenus(button.dropMenu));
+						}
+					}
+				}); 
+				levels.pop();
+				return ul;
+			}
+
+			// markItUp! markups
+			function magicMarkups(string) {
+				if (string) {
+					string = string.toString();
+					string = string.replace(/\(\!\(([\s\S]*?)\)\!\)/g,
+						function(x, a) {
+							var b = a.split('|!|');
+							if (altKey === true) {
+								return (b[1] !== undefined) ? b[1] : b[0];
+							} else {
+								return (b[1] === undefined) ? "" : b[0];
+							}
+						}
+					);
+					// [![prompt]!], [![prompt:!:value]!]
+					string = string.replace(/\[\!\[([\s\S]*?)\]\!\]/g,
+						function(x, a) {
+							var b = a.split(':!:');
+							if (abort === true) {
+								return false;
+							}
+							value = prompt(b[0], (b[1]) ? b[1] : '');
+							if (value === null) {
+								abort = true;
+							}
+							return value;
+						}
+					);
+					return string;
+				}
+				return "";
+			}
+
+			// prepare action
+			function prepare(action) {
+				if ($.isFunction(action)) {
+					action = action(hash);
+				}
+				return magicMarkups(action);
+			}
+
+			// build block to insert
+			function build(string) {
+				var openWith 			= prepare(clicked.openWith);
+				var placeHolder 		= prepare(clicked.placeHolder);
+				var replaceWith 		= prepare(clicked.replaceWith);
+				var closeWith 			= prepare(clicked.closeWith);
+				var openBlockWith 		= prepare(clicked.openBlockWith);
+				var closeBlockWith 		= prepare(clicked.closeBlockWith);
+				var multiline 			= clicked.multiline;
+				
+				if (replaceWith !== "") {
+					block = openWith + replaceWith + closeWith;
+				} else if (selection === '' && placeHolder !== '') {
+					block = openWith + placeHolder + closeWith;
+				} else {
+					string = string || selection;
+
+					var lines = selection.split(/\r?\n/), blocks = [];
+					
+					for (var l=0; l < lines.length; l++) {
+						line = lines[l];
+						var trailingSpaces;
+						if (trailingSpaces = line.match(/ *$/)) {
+							blocks.push(openWith + line.replace(/ *$/g, '') + closeWith + trailingSpaces);
+						} else {
+							blocks.push(openWith + line + closeWith);
+						}
+					}
+					
+					block = blocks.join("\n");
+				}
+
+				block = openBlockWith + block + closeBlockWith;
+
+				return {	block:block, 
+							openWith:openWith, 
+							replaceWith:replaceWith, 
+							placeHolder:placeHolder,
+							closeWith:closeWith
+					};
+			}
+
+			// define markup to insert
+			function markup(button) {
+				var len, j, n, i;
+				hash = clicked = button;
+				get();
+				$.extend(hash, {	line:"", 
+						 			root:options.root,
+									textarea:textarea, 
+									selection:(selection||''), 
+									caretPosition:caretPosition,
+									ctrlKey:ctrlKey, 
+									shiftKey:shiftKey, 
+									altKey:altKey
+								}
+							);
+				// callbacks before insertion
+				prepare(options.beforeInsert);
+				prepare(clicked.beforeInsert);
+				if ((ctrlKey === true && shiftKey === true) || button.multiline === true) {
+					prepare(clicked.beforeMultiInsert);
+				}			
+				$.extend(hash, { line:1 });
+
+				if ((ctrlKey === true && shiftKey === true)) {
+					lines = selection.split(/\r?\n/);
+					for (j = 0, n = lines.length, i = 0; i < n; i++) {
+						if ($.trim(lines[i]) !== '') {
+							$.extend(hash, { line:++j, selection:lines[i] } );
+							lines[i] = build(lines[i]).block;
+						} else {
+							lines[i] = "";
+						}
+					}
+					string = { block:lines.join('\n')};
+					start = caretPosition;
+					len = string.block.length + (($.browser.opera) ? n-1 : 0);
+				} else if (ctrlKey === true) {
+					string = build(selection);
+					start = caretPosition + string.openWith.length;
+					len = string.block.length - string.openWith.length - string.closeWith.length;
+					len = len - (string.block.match(/ $/) ? 1 : 0);
+					len -= fixIeBug(string.block);
+				} else if (shiftKey === true) {
+					string = build(selection);
+					start = caretPosition;
+					len = string.block.length;
+					len -= fixIeBug(string.block);
+				} else {
+					string = build(selection);
+					start = caretPosition + string.block.length ;
+					len = 0;
+					start -= fixIeBug(string.block);
+				}
+				if ((selection === '' && string.replaceWith === '')) {
+					caretOffset += fixOperaBug(string.block);
+					
+					start = caretPosition + string.openWith.length;
+					len = string.block.length - string.openWith.length - string.closeWith.length;
+
+					caretOffset = $$.val().substring(caretPosition,  $$.val().length).length;
+					caretOffset -= fixOperaBug($$.val().substring(0, caretPosition));
+				}
+				$.extend(hash, { caretPosition:caretPosition, scrollPosition:scrollPosition } );
+
+				if (string.block !== selection && abort === false) {
+					insert(string.block);
+					set(start, len);
+				} else {
+					caretOffset = -1;
+				}
+				get();
+
+				$.extend(hash, { line:'', selection:selection });
+
+				// callbacks after insertion
+				if ((ctrlKey === true && shiftKey === true) || button.multiline === true) {
+					prepare(clicked.afterMultiInsert);
+				}
+				prepare(clicked.afterInsert);
+				prepare(options.afterInsert);
+
+				// refresh preview if opened
+				if (previewWindow && options.previewAutoRefresh) {
+					refreshPreview(); 
+				}
+																									
+				// reinit keyevent
+				shiftKey = altKey = ctrlKey = abort = false;
+			}
+
+			// Substract linefeed in Opera
+			function fixOperaBug(string) {
+				if ($.browser.opera) {
+					return string.length - string.replace(/\n*/g, '').length;
+				}
+				return 0;
+			}
+			// Substract linefeed in IE
+			function fixIeBug(string) {
+				if ($.browser.msie) {
+					return string.length - string.replace(/\r*/g, '').length;
+				}
+				return 0;
+			}
+				
+			// add markup
+			function insert(block) {	
+				if (document.selection) {
+					var newSelection = document.selection.createRange();
+					newSelection.text = block;
+				} else {
+					textarea.value =  textarea.value.substring(0, caretPosition)  + block + textarea.value.substring(caretPosition + selection.length, textarea.value.length);
+				}
+			}
+
+			// set a selection
+			function set(start, len) {
+				if (textarea.createTextRange){
+					// quick fix to make it work on Opera 9.5
+					if ($.browser.opera && $.browser.version >= 9.5 && len == 0) {
+						return false;
+					}
+					range = textarea.createTextRange();
+					range.collapse(true);
+					range.moveStart('character', start); 
+					range.moveEnd('character', len); 
+					range.select();
+				} else if (textarea.setSelectionRange ){
+					textarea.setSelectionRange(start, start + len);
+				}
+				textarea.scrollTop = scrollPosition;
+				textarea.focus();
+			}
+
+			// get the selection
+			function get() {
+				textarea.focus();
+
+				scrollPosition = textarea.scrollTop;
+				if (document.selection) {
+					selection = document.selection.createRange().text;
+					if ($.browser.msie) { // ie
+						var range = document.selection.createRange(), rangeCopy = range.duplicate();
+						rangeCopy.moveToElementText(textarea);
+						caretPosition = -1;
+						while(rangeCopy.inRange(range)) {
+							rangeCopy.moveStart('character');
+							caretPosition ++;
+						}
+					} else { // opera
+						caretPosition = textarea.selectionStart;
+					}
+				} else { // gecko & webkit
+					caretPosition = textarea.selectionStart;
+
+					selection = textarea.value.substring(caretPosition, textarea.selectionEnd);
+				} 
+				return selection;
+			}
+
+			// open preview window
+			function preview() {
+				if (!previewWindow || previewWindow.closed) {
+					if (options.previewInWindow) {
+						previewWindow = window.open('', 'preview', options.previewInWindow);
+						$(window).unload(function() {
+							previewWindow.close();
+						});
+					} else {
+						iFrame = $('<iframe class="markItUpPreviewFrame"></iframe>');
+						if (options.previewPosition == 'after') {
+							iFrame.insertAfter(footer);
+						} else {
+							iFrame.insertBefore(header);
+						}	
+						previewWindow = iFrame[iFrame.length - 1].contentWindow || frame[iFrame.length - 1];
+					}
+				} else if (altKey === true) {
+					if (iFrame) {
+						iFrame.remove();
+					} else {
+						previewWindow.close();
+					}
+					previewWindow = iFrame = false;
+				}
+				if (!options.previewAutoRefresh) {
+					refreshPreview(); 
+				}
+				if (options.previewInWindow) {
+					previewWindow.focus();
+				}
+			}
+
+			// refresh Preview window
+			function refreshPreview() {
+ 				renderPreview();
+			}
+
+			function renderPreview() {		
+				var phtml;
+				if (options.previewParser && typeof options.previewParser === 'function') {
+					var data = options.previewParser( $$.val() );
+					writeInPreview( localize(data, 1) ); 
+				} else if (options.previewParserPath !== '') {
+					$.ajax({
+						type: 'POST',
+						dataType: 'text',
+						global: false,
+						url: options.previewParserPath,
+						data: options.previewParserVar+'='+encodeURIComponent($$.val()),
+						success: function(data) {
+							writeInPreview( localize(data, 1) ); 
+						}
+					});
+				} else {
+					if (!template) {
+						$.ajax({
+							url: options.previewTemplatePath,
+							dataType: 'text',
+							global: false,
+							success: function(data) {
+								writeInPreview( localize(data, 1).replace(/<!-- content -->/g, $$.val()) );
+							}
+						});
+					}
+				}
+				return false;
+			}
+			
+			function writeInPreview(data) {
+				if (previewWindow.document) {			
+					try {
+						sp = previewWindow.document.documentElement.scrollTop
+					} catch(e) {
+						sp = 0;
+					}	
+					previewWindow.document.open();
+					previewWindow.document.write(data);
+					previewWindow.document.close();
+					previewWindow.document.documentElement.scrollTop = sp;
+				}
+			}
+			
+			// set keys pressed
+			function keyPressed(e) { 
+				shiftKey = e.shiftKey;
+				altKey = e.altKey;
+				ctrlKey = (!(e.altKey && e.ctrlKey)) ? (e.ctrlKey || e.metaKey) : false;
+
+				if (e.type === 'keydown') {
+					if (ctrlKey === true) {
+						li = $('a[accesskey="'+String.fromCharCode(e.keyCode)+'"]', header).parent('li');
+						if (li.length !== 0) {
+							ctrlKey = false;
+							setTimeout(function() {
+								li.triggerHandler('mouseup');
+							},1);
+							return false;
+						}
+					}
+					if (e.keyCode === 13 || e.keyCode === 10) { // Enter key
+						if (ctrlKey === true) {  // Enter + Ctrl
+							ctrlKey = false;
+							markup(options.onCtrlEnter);
+							return options.onCtrlEnter.keepDefault;
+						} else if (shiftKey === true) { // Enter + Shift
+							shiftKey = false;
+							markup(options.onShiftEnter);
+							return options.onShiftEnter.keepDefault;
+						} else { // only Enter
+							markup(options.onEnter);
+							return options.onEnter.keepDefault;
+						}
+					}
+					if (e.keyCode === 9) { // Tab key
+						if (shiftKey == true || ctrlKey == true || altKey == true) {
+							return false; 
+						}
+						if (caretOffset !== -1) {
+							get();
+							caretOffset = $$.val().length - caretOffset;
+							set(caretOffset, 0);
+							caretOffset = -1;
+							return false;
+						} else {
+							markup(options.onTab);
+							return options.onTab.keepDefault;
+						}
+					}
+				}
+			}
+
+			init();
+		});
+	};
+
+	$.fn.markItUpRemove = function() {
+		return this.each(function() {
+				var $$ = $(this).unbind().removeClass('markItUpEditor');
+				$$.parent('div').parent('div.markItUp').parent('div').replaceWith($$);
+			}
+		);
+	};
+
+	$.markItUp = function(settings) {
+		var options = { target:false };
+		$.extend(options, settings);
+		if (options.target) {
+			return $(options.target).each(function() {
+				$(this).focus();
+				$(this).trigger('insertion', [options]);
+			});
+		} else {
+			$('textarea').trigger('insertion', [options]);
+		}
+	};
+})(jQuery);

=== added file 'lava_markitup/static/lava_markitup/preview.css'
--- lava_markitup/static/lava_markitup/preview.css	1970-01-01 00:00:00 +0000
+++ lava_markitup/static/lava_markitup/preview.css	2011-09-23 08:17:10 +0000
@@ -0,0 +1,50 @@ 
+body, input, th, td {
+  font-family: Ubuntu, Helvetica Neue, Verdana, sans-serif;
+  font-size: 10pt;
+}
+
+code {
+  font-family: UbuntuBeta Mono, Lucida Console, Consolas, monospace;
+  font-size: 10pt;
+}
+
+body {
+  color: #444;
+  padding: 1ex;
+  margin: 0;
+  background-color: white;
+}
+
+a {
+  color: #08C;
+}
+
+
+h1 {
+  color: #222;
+  border-bottom: 3pt solid #444;
+}
+
+h2 {
+  color: #222;
+  border-bottom: 2pt solid #444;
+}
+
+h3 {
+  color: #111;
+}
+
+blockquote {
+  border-left: 2pt solid #555;
+  padding-left: 1ex;
+  margin-left: 2ex;
+}
+
+
+code {
+  display: block;
+  font-family: UbuntuBeta Mono, monospace;
+  background-color: #888;
+  color: white;
+  padding: 0.5ex;
+}

=== added directory 'lava_markitup/static/lava_markitup/sets'
=== added directory 'lava_markitup/static/lava_markitup/sets/markdown'
=== added directory 'lava_markitup/static/lava_markitup/sets/markdown/images'
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/bold.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/bold.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/bold.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/code.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/code.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/code.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/h1.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/h1.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/h1.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/h2.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/h2.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/h2.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/h3.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/h3.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/h3.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/h4.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/h4.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/h4.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/h5.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/h5.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/h5.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/h6.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/h6.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/h6.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/italic.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/italic.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/italic.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/link.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/link.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/link.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/list-bullet.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/list-bullet.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/list-bullet.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/list-numeric.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/list-numeric.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/list-numeric.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/picture.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/picture.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/picture.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/preview.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/preview.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/preview.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/images/quotes.png'
Binary files lava_markitup/static/lava_markitup/sets/markdown/images/quotes.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/sets/markdown/images/quotes.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/sets/markdown/set.js'
--- lava_markitup/static/lava_markitup/sets/markdown/set.js	1970-01-01 00:00:00 +0000
+++ lava_markitup/static/lava_markitup/sets/markdown/set.js	2011-09-23 08:17:10 +0000
@@ -0,0 +1,52 @@ 
+// -------------------------------------------------------------------
+// markItUp!
+// -------------------------------------------------------------------
+// Copyright (C) 2008 Jay Salvat
+// http://markitup.jaysalvat.com/
+// -------------------------------------------------------------------
+// MarkDown tags example
+// http://en.wikipedia.org/wiki/Markdown
+// http://daringfireball.net/projects/markdown/
+// -------------------------------------------------------------------
+// Feel free to add more tags
+// -------------------------------------------------------------------
+mySettings = {
+	previewParserPath:	'',
+	onShiftEnter:		{keepDefault:false, openWith:'\n\n'},
+	markupSet: [
+		{name:'First Level Heading', key:'1', placeHolder:'Your title here...', closeWith:function(markItUp) { return miu.markdownTitle(markItUp, '=') } },
+		{name:'Second Level Heading', key:'2', placeHolder:'Your title here...', closeWith:function(markItUp) { return miu.markdownTitle(markItUp, '-') } },
+		{name:'Heading 3', key:'3', openWith:'### ', placeHolder:'Your title here...' },
+		{name:'Heading 4', key:'4', openWith:'#### ', placeHolder:'Your title here...' },
+		{name:'Heading 5', key:'5', openWith:'##### ', placeHolder:'Your title here...' },
+		{name:'Heading 6', key:'6', openWith:'###### ', placeHolder:'Your title here...' },
+		{separator:'---------------' },		
+		{name:'Bold', key:'B', openWith:'**', closeWith:'**'},
+		{name:'Italic', key:'I', openWith:'_', closeWith:'_'},
+		{separator:'---------------' },
+		{name:'Bulleted List', openWith:'- ' },
+		{name:'Numeric List', openWith:function(markItUp) {
+			return markItUp.line+'. ';
+		}},
+		{separator:'---------------' },
+		{name:'Picture', key:'P', replaceWith:'![[![Alternative text]!]]([![Url:!:http://]!] "[![Title]!]")'},
+		{name:'Link', key:'L', openWith:'[', closeWith:']([![Url:!:http://]!] "[![Title]!]")', placeHolder:'Your text to link here...' },
+		{separator:'---------------'},	
+		{name:'Quotes', openWith:'> '},
+		{name:'Code Block / Code', openWith:'(!(\t|!|`)!)', closeWith:'(!(`)!)'},
+		{separator:'---------------'},
+		{name:'Preview', call:'preview', className:"preview"}
+	]
+}
+
+// mIu nameSpace to avoid conflict.
+miu = {
+	markdownTitle: function(markItUp, char) {
+		heading = '';
+		n = $.trim(markItUp.selection||markItUp.placeHolder).length;
+		for(i = 0; i < n; i++) {
+			heading += char;
+		}
+		return '\n'+heading;
+	}
+}
\ No newline at end of file

=== added file 'lava_markitup/static/lava_markitup/sets/markdown/style.css'
--- lava_markitup/static/lava_markitup/sets/markdown/style.css	1970-01-01 00:00:00 +0000
+++ lava_markitup/static/lava_markitup/sets/markdown/style.css	2011-09-23 08:17:10 +0000
@@ -0,0 +1,54 @@ 
+/* -------------------------------------------------------------------
+// markItUp!
+// By Jay Salvat - http://markitup.jaysalvat.com/
+// ------------------------------------------------------------------*/
+.markItUp .markItUpButton1 a {
+	background-image:url(images/h1.png); 
+}
+.markItUp .markItUpButton2 a {
+	background-image:url(images/h2.png); 
+}
+.markItUp .markItUpButton3 a {
+	background-image:url(images/h3.png); 
+}
+.markItUp .markItUpButton4 a {
+	background-image:url(images/h4.png); 
+}
+.markItUp .markItUpButton5 a {
+	background-image:url(images/h5.png); 
+}
+.markItUp .markItUpButton6 a {
+	background-image:url(images/h6.png); 
+}
+
+.markItUp .markItUpButton7 a {
+	background-image:url(images/bold.png);
+}
+.markItUp .markItUpButton8 a {
+	background-image:url(images/italic.png);
+}
+
+.markItUp .markItUpButton9 a {
+	background-image:url(images/list-bullet.png);
+}
+.markItUp .markItUpButton10 a {
+	background-image:url(images/list-numeric.png);
+}
+
+.markItUp .markItUpButton11 a {
+	background-image:url(images/picture.png); 
+}
+.markItUp .markItUpButton12 a {
+	background-image:url(images/link.png);
+}
+
+.markItUp .markItUpButton13 a	{
+	background-image:url(images/quotes.png);
+}
+.markItUp .markItUpButton14 a	{
+	background-image:url(images/code.png);
+}
+
+.markItUp .preview a {
+	background-image:url(images/preview.png);
+}
\ No newline at end of file

=== added directory 'lava_markitup/static/lava_markitup/skins'
=== added directory 'lava_markitup/static/lava_markitup/skins/simple'
=== added directory 'lava_markitup/static/lava_markitup/skins/simple/images'
=== added file 'lava_markitup/static/lava_markitup/skins/simple/images/handle.png'
Binary files lava_markitup/static/lava_markitup/skins/simple/images/handle.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/skins/simple/images/handle.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/skins/simple/images/menu.png'
Binary files lava_markitup/static/lava_markitup/skins/simple/images/menu.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/skins/simple/images/menu.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/skins/simple/images/submenu.png'
Binary files lava_markitup/static/lava_markitup/skins/simple/images/submenu.png	1970-01-01 00:00:00 +0000 and lava_markitup/static/lava_markitup/skins/simple/images/submenu.png	2011-09-23 08:17:10 +0000 differ
=== added file 'lava_markitup/static/lava_markitup/skins/simple/style.css'
--- lava_markitup/static/lava_markitup/skins/simple/style.css	1970-01-01 00:00:00 +0000
+++ lava_markitup/static/lava_markitup/skins/simple/style.css	2011-09-23 08:17:10 +0000
@@ -0,0 +1,118 @@ 
+/* -------------------------------------------------------------------
+// markItUp! Universal MarkUp Engine, JQuery plugin
+// By Jay Salvat - http://markitup.jaysalvat.com/
+// ------------------------------------------------------------------*/
+.markItUp * {
+	margin:0px; padding:0px;
+	outline:none;
+}
+.markItUp a:link,
+.markItUp a:visited {
+	color:#000;
+	text-decoration:none;
+}
+.markItUp  {
+	width:700px;
+	margin:5px 0 5px 0;
+}
+.markItUpContainer  {
+	font:11px Verdana, Arial, Helvetica, sans-serif;
+}
+.markItUpEditor {
+	font:12px 'Courier New', Courier, monospace;
+	padding:5px;
+	width:690px;
+	height:320px;
+	clear:both;
+	line-height:18px;
+	overflow:auto;
+}
+.markItUpPreviewFrame	{
+	overflow:auto;
+	background-color:#FFF;
+	width:99.9%;
+	height:300px;
+	margin:5px 0;
+}
+.markItUpFooter {
+	width:100%;
+}
+.markItUpResizeHandle {
+	overflow:hidden;
+	width:22px; height:5px;
+	margin-left:auto;
+	margin-right:auto;
+	background-image:url(images/handle.png);
+	cursor:n-resize;
+}
+/***************************************************************************************/
+/* first row of buttons */
+.markItUpHeader ul li	{
+	list-style:none;
+	float:left;
+	position:relative;
+}
+.markItUpHeader ul li:hover > ul{
+	display:block;
+}
+.markItUpHeader ul .markItUpDropMenu {
+	background:transparent url(images/menu.png) no-repeat 115% 50%;
+	margin-right:5px;
+}
+.markItUpHeader ul .markItUpDropMenu li {
+	margin-right:0px;
+}
+/* next rows of buttons */
+.markItUpHeader ul ul {
+	display:none;
+	position:absolute;
+	top:18px; left:0px;	
+	background:#FFF;
+	border:1px solid #000;
+}
+.markItUpHeader ul ul li {
+	float:none;
+	border-bottom:1px solid #000;
+}
+.markItUpHeader ul ul .markItUpDropMenu {
+	background:#FFF url(images/submenu.png) no-repeat 100% 50%;
+}
+.markItUpHeader ul .markItUpSeparator {
+	margin:0 10px;
+	width:1px;
+	height:16px;
+	overflow:hidden;
+	background-color:#CCC;
+}
+.markItUpHeader ul ul .markItUpSeparator {
+	width:auto; height:1px;
+	margin:0px;
+}
+/* next rows of buttons */
+.markItUpHeader ul ul ul {
+	position:absolute;
+	top:-1px; left:150px; 
+}
+.markItUpHeader ul ul ul li {
+	float:none;
+}
+.markItUpHeader ul a {
+	display:block;
+	width:16px; height:16px;
+	text-indent:-10000px;
+	background-repeat:no-repeat;
+	padding:3px;
+	margin:0px;
+}
+.markItUpHeader ul ul a {
+	display:block;
+	padding-left:0px;
+	text-indent:0;
+	width:120px; 
+	padding:5px 5px 5px 25px;
+	background-position:2px 50%;
+}
+.markItUpHeader ul ul a:hover  {
+	color:#FFF;
+	background-color:#000;
+}

=== added directory 'lava_markitup/static/lava_markitup/templates'
=== added file 'lava_markitup/static/lava_markitup/templates/preview.css'
--- lava_markitup/static/lava_markitup/templates/preview.css	1970-01-01 00:00:00 +0000
+++ lava_markitup/static/lava_markitup/templates/preview.css	2011-09-23 08:17:10 +0000
@@ -0,0 +1,5 @@ 
+/* preview style examples */
+body {
+	background-color:#EFEFEF;
+	font:70% Verdana, Arial, Helvetica, sans-serif;
+}
\ No newline at end of file

=== added file 'lava_markitup/static/lava_markitup/templates/preview.html'
--- lava_markitup/static/lava_markitup/templates/preview.html	1970-01-01 00:00:00 +0000
+++ lava_markitup/static/lava_markitup/templates/preview.html	2011-09-23 08:17:10 +0000
@@ -0,0 +1,11 @@ 
+<!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">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+<title>markItUp! preview template</title>
+<link rel="stylesheet" type="text/css" href="~/templates/preview.css" />
+</head>
+<body>
+<!-- content -->
+</body>
+</html>

=== added directory 'lava_markitup/templates'
=== added directory 'lava_markitup/templates/lava_markitup'
=== added file 'lava_markitup/templates/lava_markitup/preview.html'
--- lava_markitup/templates/lava_markitup/preview.html	1970-01-01 00:00:00 +0000
+++ lava_markitup/templates/lava_markitup/preview.html	2011-09-23 08:17:10 +0000
@@ -0,0 +1,12 @@ 
+<!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" lang="{{ LANGUAGE_CODE }}" xml:lang="{{ LANGUAGE_CODE }}" {% if LANGUAGE_BIDI %}dir="rtl"{% endif %}>
+  <head>
+    <meta http-equiv="Content-Type" content="text/html;charset=utf-8" />
+    <title>Preview</title>
+    <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava_markitup/preview.css"/>
+  </head>
+  <body>
+    {{ markup|safe }}
+  </body>
+</html>
+

=== added file 'lava_markitup/tests.py'
=== added file 'lava_markitup/urls.py'
--- lava_markitup/urls.py	1970-01-01 00:00:00 +0000
+++ lava_markitup/urls.py	2011-09-23 08:17:10 +0000
@@ -0,0 +1,7 @@ 
+from django.conf.urls.defaults import *
+
+
+urlpatterns = patterns(
+    'lava_markitup.views',
+    url('^markdown/$', 'preview_markdown', name='lava.markitup.markdown'),
+)

=== added file 'lava_markitup/views.py'
--- lava_markitup/views.py	1970-01-01 00:00:00 +0000
+++ lava_markitup/views.py	2011-09-23 08:17:10 +0000
@@ -0,0 +1,18 @@ 
+from django.contrib.csrf.middleware import csrf_exempt
+from django.http import HttpResponse
+from django.template import RequestContext, loader
+from markdown import markdown
+
+
+@csrf_exempt
+def preview_markdown(request):
+    """
+    Convert markdown to HTML
+    """
+    markup = request.POST.get("data", "")
+    template_name = "lava_markitup/preview.html"
+    t = loader.get_template(template_name)
+    c = RequestContext(request, {
+        'markup': markdown(markup, safe_mode="escape")
+    })
+    return HttpResponse(t.render(c))

=== added directory 'lava_projects'
=== added file 'lava_projects/__init__.py'
--- lava_projects/__init__.py	1970-01-01 00:00:00 +0000
+++ lava_projects/__init__.py	2011-09-23 08:29:48 +0000
@@ -0,0 +1,19 @@ 
+# Copyright (C) 2010, 2011 Linaro Limited
+#
+# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
+#
+# This file is part of LAVA Server.
+#
+# LAVA Server is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# LAVA Server is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with LAVA Server.  If not, see <http://www.gnu.org/licenses/>.
+
+__version__ = (0, 1, 0, "dev", 0)

=== added file 'lava_projects/admin.py'
--- lava_projects/admin.py	1970-01-01 00:00:00 +0000
+++ lava_projects/admin.py	2011-09-24 19:18:28 +0000
@@ -0,0 +1,33 @@ 
+# Copyright (C) 2010, 2011 Linaro Limited
+#
+# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
+#
+# This file is part of LAVA Server.
+#
+# LAVA Server is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# LAVA Server is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with LAVA Server.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.contrib import admin
+
+from lava_projects.models import Project, ProjectFormerIdentifier 
+
+
+class ProjectAdmin(admin.ModelAdmin):
+    list_display=('__unicode__', 'identifier', 'registered_by', 'registered_on', 'is_public', 'owner')
+
+
+class ProjectFormerIdentifierAdmin(admin.ModelAdmin):
+    list_display=('former_identifier', 'project', 'renamed_on', 'renamed_by')
+
+
+admin.site.register(Project, ProjectAdmin)
+admin.site.register(ProjectFormerIdentifier, ProjectFormerIdentifierAdmin)

=== added file 'lava_projects/extension.py'
--- lava_projects/extension.py	1970-01-01 00:00:00 +0000
+++ lava_projects/extension.py	2011-09-23 08:29:48 +0000
@@ -0,0 +1,47 @@ 
+# Copyright (C) 2010, 2011 Linaro Limited
+#
+# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
+#
+# This file is part of LAVA Server.
+#
+# LAVA Server is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# LAVA Server is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with LAVA Server.  If not, see <http://www.gnu.org/licenses/>.
+
+from lava_server.extension import LavaServerExtension
+
+
+class ProjectExtension(LavaServerExtension):
+    """
+    Extension adding project support
+    """
+
+    @property
+    def app_name(self):
+        return "lava_projects"
+
+    @property
+    def name(self):
+        return "Projects"
+
+    @property
+    def main_view_name(self):
+        return "lava.project.root"
+
+    @property
+    def description(self):
+        return "Project support for LAVA"
+
+    @property
+    def version(self):
+        from lava_projects import __version__
+        import versiontools
+        return versiontools.format_version(__version__)

=== added file 'lava_projects/forms.py'
--- lava_projects/forms.py	1970-01-01 00:00:00 +0000
+++ lava_projects/forms.py	2011-09-24 19:18:28 +0000
@@ -0,0 +1,173 @@ 
+# Copyright (C) 2010, 2011 Linaro Limited
+#
+# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
+#
+# This file is part of LAVA Server.
+#
+# LAVA Server is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# LAVA Server is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with LAVA Server.  If not, see <http://www.gnu.org/licenses/>.
+
+from django import forms
+from django.contrib.auth.models import Group
+from django.core.exceptions import ValidationError
+from django.utils.translation import ugettext as _
+
+from lava_projects.models import Project
+
+
+class _ProjectForm(forms.Form):
+    """
+    Mix-in with common project fields.
+    """
+
+    name = forms.CharField(
+        label=_(u"Projet name"),
+        help_text=_(u"Any name of your liking, can be updated later if you"
+                    u" change your mind"),
+        required=True,
+        max_length=100)
+
+    description = forms.CharField(
+        label=_(u"Description"),
+        help_text=_(u"Arbitrary text about the project, you can use markdown"
+                    u" formatting to style it"),
+        widget=forms.widgets.Textarea(),
+        required=False)
+
+    group = forms.ModelChoiceField(
+        label=_(u"Group owner"),
+        help_text=_(u"Members of the selected group will co-own this project"),
+        empty_label=_("None, I'll be the owner"),
+        required=False,
+        queryset=Group.objects.all())
+
+    is_public = forms.BooleanField(
+        label=_(u"Project is public"),
+        help_text=_(u"If selected then this project will be visible to anyone."
+                    u" Otherwise only owners can see the project"),
+        required=False)
+
+    is_aggregate = forms.BooleanField(
+        label=_(u"Project is an aggregation (distribution)"),
+        help_text=_(u"If selected the project will be treated like a"
+                    u" distribution. Some UI elements are optimized for that case"
+                    u" and behave differently."),
+        required=False)
+
+    def restrict_group_selection_for_user(self, user):
+        assert user is not None
+        self.fields['group'].queryset = user.groups.all()
+
+
+class ProjectRegistrationForm(_ProjectForm):
+    """
+    Form for registering new projects.
+    """
+
+    identifier = forms.CharField(
+        label=_(u"Identifier"),
+        help_text=_(u"A unique identifier built from restricted subset of"
+                    u" characters (only basic lowercase letters, numbers and"
+                    u" dash)"),
+        required=True,
+        max_length=100)
+
+    def __init__(self, *args, **kwargs):
+        super(ProjectRegistrationForm, self).__init__(*args, **kwargs)
+        self._reorder_fields(['name', 'identifier'])
+
+    def _reorder_fields(self, first):
+        for field in reversed(first):
+            self.fields.keyOrder.remove(field)
+            self.fields.keyOrder.insert(0, field)
+
+    def clean_identifier(self):
+        """
+        Check that the identifier is correct:
+            
+            1) It does not collide with other projects
+            2) Or their past identifiers
+        """
+        value = self.cleaned_data['identifier']
+        try:
+            # Lookup project that is, or was, this identifier
+            project = Project.objects.all().get_by_identifier(value)
+            if project.identifier == value:
+                # Disallow current identifiers from other projects
+                raise ValidationError("Project %s is already using this identifier" % project)
+            else: 
+                # Disallow past identifiers from other projects
+                raise ValidationError("Project %s was using this identifier in the past" % project)
+        except Project.DoesNotExist:
+            pass
+        return value
+
+
+class ProjectUpdateForm(_ProjectForm):
+    """
+    Form for updating project data
+    """
+
+
+class ProjectRenameForm(forms.Form):
+    """
+    Form for changing the project identifier
+    """
+
+    name = forms.CharField(
+        label=_(u"Projet name"),
+        help_text=_(u"The new project name, same limits as before (100 chars)"),
+        required=True,
+        max_length=100)
+
+    identifier = forms.CharField(
+        label=_(u"New identifier"),
+        help_text=_(u"The new identifier has to be different from any current"
+                    u" or past identifier used by other projects."),
+        required=True,
+        max_length=100)
+
+    def __init__(self, project, *args, **kwargs):
+        super(ProjectRenameForm, self).__init__(*args, **kwargs)
+        self.project = project
+
+    def clean_identifier(self):
+        """
+        Check that new identifier is correct:
+            
+            1) It does not collide with other projects
+            2) Or their past identifiers
+            3) It is different than the one we are currently using
+        """
+        value = self.cleaned_data['identifier']
+        try:
+            # Lookup project that is, or was, using this identifier
+            project = Project.objects.all().get_by_identifier(value)
+            if project == self.project and project.identifier != value:
+                # Allow reusing identifiers inside one project
+                pass
+            elif project == self.project and project.identifier == value:
+                raise ValidationError(
+                    _(u"The new identifier has to be different than the one"
+                      u"you are currently using"))
+            elif project.identifier == value:
+                # Disallow current identifiers from other projects
+                raise ValidationError(
+                    _(u"Project %s is already using this identifier") % project)
+            else: 
+                # Disallow past identifiers from other projects
+                raise ValidationError(
+                    _(u"Project %s was using this identifier in the past") %
+                    project)
+        except Project.DoesNotExist:
+            pass
+        return value

=== added directory 'lava_projects/migrations'
=== added file 'lava_projects/migrations/0001_add_model_Project.py'
--- lava_projects/migrations/0001_add_model_Project.py	1970-01-01 00:00:00 +0000
+++ lava_projects/migrations/0001_add_model_Project.py	2011-09-23 08:29:48 +0000
@@ -0,0 +1,91 @@ 
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding model 'Project'
+        db.create_table('lava_projects_project', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], null=True, blank=True)),
+            ('group', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.Group'], null=True, blank=True)),
+            ('is_public', self.gf('django.db.models.fields.BooleanField')(default=False)),
+            ('name', self.gf('django.db.models.fields.CharField')(max_length=100)),
+            ('identifier', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=100, db_index=True)),
+            ('description', self.gf('django.db.models.fields.TextField')(blank=True)),
+            ('is_aggregate', self.gf('django.db.models.fields.BooleanField')(default=False)),
+            ('registered_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name='projects', to=orm['auth.User'])),
+            ('registered_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+        ))
+        db.send_create_signal('lava_projects', ['Project'])
+
+
+    def backwards(self, orm):
+        
+        # Deleting model 'Project'
+        db.delete_table('lava_projects_project')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'lava_projects.project': {
+            'Meta': {'object_name': 'Project'},
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'identifier': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'}),
+            'is_aggregate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'registered_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'to': "orm['auth.User']"}),
+            'registered_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'lava_projects.projectfomeridentifier': {
+            'Meta': {'object_name': 'ProjectFomerIdentifier'},
+            'former_identifier': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'former_identifiers'", 'to': "orm['lava_projects.Project']"})
+        }
+    }
+
+    complete_apps = ['lava_projects']

=== added file 'lava_projects/migrations/0002_add_model_ProjectFormerIdentifier.py'
--- lava_projects/migrations/0002_add_model_ProjectFormerIdentifier.py	1970-01-01 00:00:00 +0000
+++ lava_projects/migrations/0002_add_model_ProjectFormerIdentifier.py	2011-09-24 19:18:28 +0000
@@ -0,0 +1,88 @@ 
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+
+    def forwards(self, orm):
+        
+        # Adding model 'ProjectFormerIdentifier'
+        db.create_table('lava_projects_projectformeridentifier', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('project', self.gf('django.db.models.fields.related.ForeignKey')(related_name='former_identifiers', to=orm['lava_projects.Project'])),
+            ('former_identifier', self.gf('django.db.models.fields.SlugField')(unique=True, max_length=100, db_index=True)),
+            ('renamed_by', self.gf('django.db.models.fields.related.ForeignKey')(related_name='project_former_identifiers_created', to=orm['auth.User'])),
+            ('renamed_on', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, blank=True)),
+        ))
+        db.send_create_signal('lava_projects', ['ProjectFormerIdentifier'])
+
+
+    def backwards(self, orm):
+        
+        # Deleting model 'ProjectFormerIdentifier'
+        db.delete_table('lava_projects_projectformeridentifier')
+
+
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'lava_projects.project': {
+            'Meta': {'object_name': 'Project'},
+            'description': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'group': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.Group']", 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'identifier': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'}),
+            'is_aggregate': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'is_public': ('django.db.models.fields.BooleanField', [], {'default': 'False'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'registered_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'projects'", 'to': "orm['auth.User']"}),
+            'registered_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'null': 'True', 'blank': 'True'})
+        },
+        'lava_projects.projectformeridentifier': {
+            'Meta': {'object_name': 'ProjectFormerIdentifier'},
+            'former_identifier': ('django.db.models.fields.SlugField', [], {'unique': 'True', 'max_length': '100', 'db_index': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'project': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'former_identifiers'", 'to': "orm['lava_projects.Project']"}),
+            'renamed_by': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'project_former_identifiers_created'", 'to': "orm['auth.User']"}),
+            'renamed_on': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'blank': 'True'})
+        }
+    }
+
+    complete_apps = ['lava_projects']

=== added file 'lava_projects/migrations/__init__.py'
=== added file 'lava_projects/models.py'
--- lava_projects/models.py	1970-01-01 00:00:00 +0000
+++ lava_projects/models.py	2011-09-24 19:18:28 +0000
@@ -0,0 +1,171 @@ 
+# Copyright (C) 2010, 2011 Linaro Limited
+#
+# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
+#
+# This file is part of LAVA Server.
+#
+# LAVA Server is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# LAVA Server is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with LAVA Server.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.contrib.auth.models import User
+from django.core.exceptions import ObjectDoesNotExist
+from django.db import models
+from django.db.models.query import QuerySet
+from django.utils.translation import ugettext as _
+from django_restricted_resource.managers import RestrictedResourceManager
+from django_restricted_resource.models  import RestrictedResource
+
+
+class ProjectQuerySet(QuerySet):
+    """
+    Query set with extra methods for projects
+    """
+
+    def recently_registered(self):
+        return self.order_by("-registered_on")[:10]
+
+    def get_by_identifier(self, identifier):
+        """
+        Get project by identifier, also searching for past
+        identifiers (project renames)
+        """
+        try:
+            return self.get(identifier=identifier)
+        except Project.DoesNotExist as no_such_project:
+            try:
+                project_former_identifier = ProjectFormerIdentifier.objects.get(former_identifier=identifier)
+                return project_former_identifier.project
+            except ProjectFormerIdentifier.DoesNotExist:
+                raise no_such_project
+
+
+class ProjectManager(RestrictedResourceManager):
+    """
+    Manager with custom query set for projects
+    """
+    use_for_related_fields = True
+
+    def get_query_set(self):
+        return ProjectQuerySet(self.model, using=self._db)
+
+    def recently_registered(self):
+        return self.get_query_set().recently_registered()
+
+    def get_by_identifier(self, identifier):
+        return self.get_query_set().get_by_identifier(identifier)
+
+
+class ProjectFormerIdentifier(models.Model):
+    """
+    Former identifier of a project. Allows users to change the project
+    identifier while keeping URLs working properly.
+    """
+
+    project = models.ForeignKey(
+        "Project",
+        null=False,
+        blank=False,
+        verbose_name=_(u"Project"),
+        related_name="former_identifiers")
+
+    former_identifier = models.SlugField(
+        null=False,
+        blank=False,
+        max_length=100,
+        verbose_name=_(u"Former identifier"),
+        help_text=_(u"A unique identifier built from restricted subset of"
+                    u" characters (only basic letters, numbers and dash)"),
+        unique=True)
+
+    renamed_by = models.ForeignKey(
+        User,
+        related_name="project_former_identifiers_created",
+        blank=False,
+        null=False,
+        verbose_name=_(u"Renamed by"),
+        help_text=_(u"User who renamed the project"))
+
+    renamed_on = models.DateTimeField(
+        auto_now_add=True,
+        blank=False,
+        null=False,
+        verbose_name=_(u"Renamed on"),
+        help_text=_(u"Date and time of rename operation"))
+
+    def __unicode__(self):
+        return self.former_identifier
+
+
+class Project(RestrictedResource):
+    """
+    Project is a container of everything else. Projects are restricted
+    resources and thus belong to a particular user or group and have a "public"
+    flag.
+    """
+
+    name = models.CharField(
+        null=False,
+        blank=False,
+        max_length=100,
+        verbose_name=_(u"Name"),
+        help_text=_(u"A unique identifier built from restricted subset of"
+                    u" characters (only basic lowercase letters, numbers and"
+                    u" dash). Changing this field will break existing links"
+                    u" and is not recommended"))
+
+    identifier = models.SlugField(
+        null=False,
+        blank=False,
+        max_length=100,
+        verbose_name=_(u"Identifier"),
+        help_text=_(u"A unique identifier built from restricted subset of"
+                    u" characters (only basic letters, numbers and dash)"),
+        unique=True)
+
+    description = models.TextField(
+        null=False,
+        blank=True,
+        verbose_name = _(u"Description"),
+        help_text = _(u"Arbitrary text about the project, you can use markdown"
+                      u" formatting to style it"))
+
+    is_aggregate = models.BooleanField(
+        blank=True,
+        null=False,
+        verbose_name=_(u"Aggregate"),
+        help_text=_(u"When selected the project will be treated like a"
+                    u" distribution. Some UI elements are optimized for that case"
+                    u" and behave differently."))
+
+    registered_by = models.ForeignKey(
+        User,
+        related_name="projects",
+        blank=False,
+        null=False,
+        verbose_name=_(u"Registered by"),
+        help_text=_(u"User who registered this project"))
+
+    registered_on = models.DateTimeField(
+        auto_now_add=True,
+        blank=False,
+        null=False,
+        verbose_name=_(u"Registered on"),
+        help_text=_(u"Date and time of registration"))
+
+    objects = ProjectManager()
+
+    def __unicode__(self):
+        return self.name
+
+    @models.permalink
+    def get_absolute_url(self):
+        return ('lava.project.detail', [self.identifier])

=== added directory 'lava_projects/templates'
=== added directory 'lava_projects/templates/lava_projects'
=== added file 'lava_projects/templates/lava_projects/_project_actions.html'
--- lava_projects/templates/lava_projects/_project_actions.html	1970-01-01 00:00:00 +0000
+++ lava_projects/templates/lava_projects/_project_actions.html	2011-09-23 08:29:48 +0000
@@ -0,0 +1,5 @@ 
+<h3>Actions</h3>
+<ul>
+  <li><a href="{% url lava.project.list %}">Display a list of all projects</a></li>
+  <li><a href="{% url lava.project.register %}">Register a new project</a></li>
+</ul>

=== added file 'lava_projects/templates/lava_projects/_project_form.html'
--- lava_projects/templates/lava_projects/_project_form.html	1970-01-01 00:00:00 +0000
+++ lava_projects/templates/lava_projects/_project_form.html	2011-09-24 19:18:28 +0000
@@ -0,0 +1,47 @@ 
+{% extends "layouts/content.html" %}
+
+
+{% block extrahead %}
+{{ block.super }}
+<script type="text/javascript" src="{{ STATIC_URL }}lava-server/js/jquery.slugify.js"></script>
+<script type="text/javascript" src="{{ STATIC_URL }}lava_markitup/jquery.markitup.js"></script>
+<script type="text/javascript" src="{{ STATIC_URL }}lava_markitup/sets/markdown/set.js"></script>
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava_markitup/skins/simple/style.css"/>
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava_markitup/sets/markdown/style.css"/>
+{% endblock %}
+
+
+{% block content %}
+<style type="text/css">
+  iframe { border: 1px dotted #333; }
+</style>
+<form method="post" action="">
+  {% block form_header %}
+  {% endblock %}
+  {% csrf_token %}
+  <table class="form_helper">
+    {% for field in form %}
+    {% block form_field %}
+    <tr>
+      <th><label for="{{ field.id_for_label }}">{{ field.label }}</label></th>
+      <td>
+        {% if field.errors %}
+        <div class="ui-state-error">
+          {{ field.errors }}
+          {{ field }}
+        </div>
+        {% else %}
+        {{ field }}
+        {% endif %}
+        <p class='help_text'>{{ field.help_text }}</p>
+      </td>
+    </tr>
+    {% endblock %}
+    {% endfor %}
+  </table>
+  {% block form_footer %}
+  <h2>Continue</h2>
+  <input type="submit" value="Save"/>
+  {% endblock %}
+</form>
+{% endblock %}

=== added file 'lava_projects/templates/lava_projects/project_detail.html'
--- lava_projects/templates/lava_projects/project_detail.html	1970-01-01 00:00:00 +0000
+++ lava_projects/templates/lava_projects/project_detail.html	2011-09-24 19:18:28 +0000
@@ -0,0 +1,43 @@ 
+{% extends "layouts/content_with_sidebar.html" %}
+{% load markup %}
+
+
+{% block sidebar %}
+{% include "lava_projects/_project_actions.html" %}
+{% if belongs_to_user %}
+<h3>Project administration</h3>
+<ul>
+  <li><a href="{% url lava.project.update project.identifier %}">Update this project</a></li>
+</ul>
+{% endif %}
+{% endblock %}
+
+
+{% block content %}
+{% if former_identifier %}
+<div class="ui-state-highlight">
+  <p><span
+    class="ui-icon ui-icon-info"
+    style="float: left; margin: 2pt"></span>
+  This project was renamed to <strong>{{ project.identifier }}</strong>
+  {{ former_identifier.renamed_on|timesince }} ago.  The old URL referring to
+  <strong>{{ former_identifier.former_identifier }}</strong> will redirect here
+  automatically but you should update your bookmarks or hyper-links</p>
+</div>
+{% endif %}
+<h1>{{ project }}</h1>
+<p>
+{% if project.user == project.registered_by %}
+Owned and registered by {{ project.owner }} on {{ project.registered_on|date }}
+{% else %}
+Owned by {{ project.owner }}
+{% if project.group and belongs_to_user %}
+<em>(you are a member of this group)</em>
+{% endif %}
+, registered by {{ project.registered_by }} on {{ project.registered_on|date }}
+{% endif %}
+</p>
+<div class="project-description">
+  {{ project.description|markdown }}
+</div>
+{% endblock %}

=== added file 'lava_projects/templates/lava_projects/project_list.html'
--- lava_projects/templates/lava_projects/project_list.html	1970-01-01 00:00:00 +0000
+++ lava_projects/templates/lava_projects/project_list.html	2011-09-24 19:18:28 +0000
@@ -0,0 +1,19 @@ 
+{% extends "layouts/content_with_sidebar.html" %}
+
+
+{% block sidebar %}
+{% include "lava_projects/_project_actions.html" %}
+{% endblock %}
+
+
+{% block content %}
+<h2>Existing projects</h2>
+<ul>
+  {% for project in project_list %}
+  <li>
+  <a href="{{ project.get_absolute_url }}">{{ project }}</a>
+  {% if not project.is_public %}<strong>(private)</strong>{% endif %}
+  </li>
+  {% endfor %}
+</ul>
+{% endblock %}

=== added file 'lava_projects/templates/lava_projects/project_register_form.html'
--- lava_projects/templates/lava_projects/project_register_form.html	1970-01-01 00:00:00 +0000
+++ lava_projects/templates/lava_projects/project_register_form.html	2011-09-24 19:18:28 +0000
@@ -0,0 +1,18 @@ 
+{% extends "lava_projects/_project_form.html" %}
+
+
+{% block form_header %}
+<h1>Register a project in LAVA</h1>
+{% endblock %}
+
+
+{% block form_footer %}
+{{ block.super }}
+<script type="text/javascript">
+  mySettings.previewParserPath = "{% url lava.markitup.markdown %}";
+  $(document).ready(function() {
+    $("#id_identifier").slugify($("#id_name"));
+    $("#id_description").markItUp(mySettings);
+  });
+</script>
+{% endblock %}

=== added file 'lava_projects/templates/lava_projects/project_rename_form.html'
--- lava_projects/templates/lava_projects/project_rename_form.html	1970-01-01 00:00:00 +0000
+++ lava_projects/templates/lava_projects/project_rename_form.html	2011-09-24 19:18:28 +0000
@@ -0,0 +1,35 @@ 
+{% extends "lava_projects/_project_form.html" %}
+
+
+{% block form_header %}
+<h1>Rename {{ project }}</h1>
+<p>Renaming a project is always possible but should be done carefully.
+Each historic identifier is stored to ensure old URLs continue to work. They
+are also reserved to prevent new projects from using them.</p>
+{% endblock %}
+
+
+{% block form_field %}
+{% if field.name == "identifier" %}
+<tr>
+  <th>Old identifier</th>
+  <td>
+    <input type="text" disabled="disabled" value="{{ project.identifier }}"></input>
+    <p class='help_text'>This is your current project identifier</p>
+  </td>
+</tr>
+{% endif %}
+{{ block.super }}
+{% endblock %}
+
+
+{% block form_footer %}
+<h2>Continue</h2>
+<input type="submit" value="Save"/> or <a href="{{ project.get_absolute_url }}">cancel</a>
+<script type="text/javascript">
+  $(document).ready(function() {
+    // Remove existing value, enable slugify helper
+    $("#id_identifier").val("").slugify($("#id_name"));
+  });
+</script>
+{% endblock %}

=== added file 'lava_projects/templates/lava_projects/project_root.html'
--- lava_projects/templates/lava_projects/project_root.html	1970-01-01 00:00:00 +0000
+++ lava_projects/templates/lava_projects/project_root.html	2011-09-24 19:18:28 +0000
@@ -0,0 +1,30 @@ 
+{% extends "layouts/content_with_sidebar.html" %}
+
+
+{% block sidebar %}
+{% include "lava_projects/_project_actions.html" %}
+{% endblock %}
+
+
+{% block content %}
+<h2>Projects</h2>
+<p>LAVA uses projects to organize other data structures. Projects are similar
+to the project concept in other development tools and web applications.</p>
+
+<p>Anyone is free to <a href="{% url lava.project.register %}">register a
+  project</a> and use that to coordinate testing efforts.</p>
+
+{% if recent_project_list %}
+<h3>Recently registered projects</h3>
+<ul>
+  {% for project in recent_project_list %}
+  <li>
+  <a href="{{ project.get_absolute_url }}">{{ project }}</a>,
+  registered {{ project.registered_on|timesince }} ago
+  {% if not project.is_public %}<strong>(private)</strong>{% endif %}
+  </li>
+  {% endfor %}
+</ul>
+<p><a href="{% url lava.project.list %}">See all projects</a></p>
+{% endif %}
+{% endblock %}

=== added file 'lava_projects/templates/lava_projects/project_update_form.html'
--- lava_projects/templates/lava_projects/project_update_form.html	1970-01-01 00:00:00 +0000
+++ lava_projects/templates/lava_projects/project_update_form.html	2011-09-24 19:18:28 +0000
@@ -0,0 +1,33 @@ 
+{% extends "lava_projects/_project_form.html" %}
+
+
+{% block form_header %}
+<h1>Update {{ project }}</h1>
+{% endblock %}
+
+
+{% block form_field %}
+{{ block.super }}
+{% if field.name == "name" %}
+<tr>
+  <th>Identifier<br/><small>(read only)</small></th>
+  <td>
+    <input type="text" disabled="disabled" value="{{ project.identifier }}"></input>
+    <p class='help_text'>
+    <a href="{% url lava.project.rename project.identifier %}">Rename this project</a> if you wish to change the identifier</p>
+  </td>
+</tr>
+{% endif %}
+{% endblock %}
+
+
+{% block form_footer %}
+<h2>Continue</h2>
+<input type="submit" value="Save"/> or <a href="{{ project.get_absolute_url }}">cancel</a>
+<script type="text/javascript">
+  mySettings.previewParserPath = "{% url lava.markitup.markdown %}";
+  $(document).ready(function() {
+    $("#id_description").markItUp(mySettings);
+  });
+</script>
+{% endblock %}

=== added file 'lava_projects/urls.py'
--- lava_projects/urls.py	1970-01-01 00:00:00 +0000
+++ lava_projects/urls.py	2011-09-24 19:18:28 +0000
@@ -0,0 +1,30 @@ 
+# Copyright (C) 2010, 2011 Linaro Limited
+#
+# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
+#
+# This file is part of LAVA Server.
+#
+# LAVA Server is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# LAVA Server is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with LAVA Server.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.conf.urls.defaults import *
+
+
+urlpatterns = patterns(
+    'lava_projects.views',
+    url('^$', 'project_root', name='lava.project.root'),
+    url(r'^\+list/$', 'project_list', name='lava.project.list'),
+    url(r'^\+register/$', 'project_register', name='lava.project.register'),
+    url(r'^(?P<identifier>[a-z0-9-]+)/$', 'project_detail', name='lava.project.detail'),
+    url(r'^(?P<identifier>[a-z0-9-]+)/\+update/$', 'project_update', name='lava.project.update'),
+    url(r'^(?P<identifier>[a-z0-9-]+)/\+rename/$', 'project_rename', name='lava.project.rename'),
+)

=== added file 'lava_projects/views.py'
--- lava_projects/views.py	1970-01-01 00:00:00 +0000
+++ lava_projects/views.py	2011-09-24 19:18:28 +0000
@@ -0,0 +1,179 @@ 
+# Copyright (C) 2010, 2011 Linaro Limited
+#
+# Author: Zygmunt Krynicki <zygmunt.krynicki@linaro.org>
+#
+# This file is part of LAVA Server.
+#
+# LAVA Server is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License version 3
+# as published by the Free Software Foundation
+#
+# LAVA Server is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with LAVA Server.  If not, see <http://www.gnu.org/licenses/>.
+
+from django.contrib.auth.decorators import login_required
+from django.http import HttpResponse, HttpResponseRedirect, HttpResponseForbidden
+from django.shortcuts import get_object_or_404
+from django.template import RequestContext, loader
+from django.utils.translation import ugettext as _
+from django.views.generic.list_detail import object_list, object_detail
+
+from lava_projects.models import (
+    Project,
+    ProjectFormerIdentifier,
+)
+from lava_projects.forms import (
+    ProjectRenameForm,
+    ProjectRegistrationForm,
+    ProjectUpdateForm,
+)
+
+
+def project_root(request):
+    template_name = "lava_projects/project_root.html"
+    t = loader.get_template(template_name)
+    c = RequestContext(request, {
+        'recent_project_list': Project.objects.accessible_by_principal(request.user).recently_registered()
+    })
+    return HttpResponse(t.render(c))
+
+
+def project_list(request):
+    return object_list(
+        request,
+        queryset=Project.objects.accessible_by_principal(request.user),
+        template_name="lava_projects/project_list.html",
+        template_object_name="project")
+
+
+def project_detail(request, identifier):
+    # A get by identifier, looking at renames, if needed.
+    try:
+        project = Project.objects.accessible_by_principal(request.user).get_by_identifier(identifier)
+    except Project.DoesNotExist:
+        raise Http404("No such project")
+    # Redirect users to proper URL of this project if using one of the older names.
+    if project.identifier != identifier:
+        return HttpResponseRedirect(project.get_absolute_url() + "?former_identifier=" + identifier)
+    # Lookup former identifier if we have been redirected
+    former_identifier = None
+    if request.GET.get("former_identifier"):
+        try:
+            former_identifier = ProjectFormerIdentifier.objects.get(
+                former_identifier=request.GET.get("former_identifier"))
+        except ProjectFormerIdentifier.DoesNotExist:
+            pass
+    # Render to template
+    template_name = "lava_projects/project_detail.html"
+    t = loader.get_template(template_name)
+    c = RequestContext(request, {
+        'project': project,
+        'former_identifier': former_identifier, 
+        'belongs_to_user': project.is_owned_by(request.user),
+    })
+    return HttpResponse(t.render(c))
+
+
+@login_required
+def project_register(request):
+    if request.method == 'POST':
+        form = ProjectRegistrationForm(request.POST, request.FILES)
+        form.restrict_group_selection_for_user(request.user)
+        # Check the form
+        if form.is_valid():
+            # And make a project instance
+            project = Project.objects.create(
+                name=form.cleaned_data['name'],
+                identifier=form.cleaned_data['identifier'],
+                description=form.cleaned_data['description'],
+                is_aggregate=form.cleaned_data['is_aggregate'],
+                owner=form.cleaned_data['group'] or request.user,
+                is_public=form.cleaned_data['is_public'],
+                registered_by=request.user)
+            return HttpResponseRedirect(project.get_absolute_url())
+    else:
+        form = ProjectRegistrationForm()
+    # Render to template
+    template_name = "lava_projects/project_register_form.html"
+    t = loader.get_template(template_name)
+    c = RequestContext(request, {
+        'form': form,
+    })
+    return HttpResponse(t.render(c))
+
+
+@login_required
+def project_update(request, identifier):
+    project = get_object_or_404(
+        Project.objects.accessible_by_principal(request.user), 
+        identifier=identifier)
+    if not project.is_owned_by(request.user):
+        return HttpResponseForbidden("You cannot update this project")
+    if request.method == 'POST':
+        form = ProjectUpdateForm(request.POST, request.FILES)
+        form.restrict_group_selection_for_user(request.user)
+        if form.is_valid():
+            project.name = form.cleaned_data['name']
+            project.description = form.cleaned_data['description']
+            project.is_aggregate = form.cleaned_data['is_aggregate']
+            project.owner = form.cleaned_data['group'] or request.user
+            project.is_public = form.cleaned_data['is_public']
+            project.save()
+            return HttpResponseRedirect(project.get_absolute_url())
+    else:
+        form = ProjectUpdateForm(initial=dict(
+            name=project.name,
+            description=project.description,
+            is_aggregate=project.is_aggregate,
+            group=project.group,
+            is_public=project.is_public))
+    template_name = "lava_projects/project_update_form.html"
+    t = loader.get_template(template_name)
+    c = RequestContext(request, {
+        'form': form,
+        'project': project,
+    })
+    return HttpResponse(t.render(c))
+
+
+@login_required
+def project_rename(request, identifier):
+    project = get_object_or_404(
+        Project.objects.accessible_by_principal(request.user), 
+        identifier=identifier)
+    if not project.is_owned_by(request.user):
+        return HttpResponseForbidden("You cannot update this project")
+    if request.method == 'POST':
+        form = ProjectRenameForm(project, request.POST)
+        if form.is_valid():
+            # Remove old entry if we are reusing our older identifier 
+            pfi = ProjectFormerIdentifier.objects.filter(
+                former_identifier=form.cleaned_data['identifier'],
+                project=project.pk).delete()
+            # Record the change taking place
+            ProjectFormerIdentifier.objects.create(
+                project=project,
+                former_identifier=project.identifier,
+                renamed_by=request.user)
+            # And update the project
+            project.name = form.cleaned_data['name']
+            project.identifier = form.cleaned_data['identifier']
+            project.save()
+            return HttpResponseRedirect(project.get_absolute_url())
+    else:
+        form = ProjectRenameForm(
+            project, initial={
+                'name': project.name,
+                'identifier': project.identifier})
+    template_name = "lava_projects/project_rename_form.html"
+    t = loader.get_template(template_name)
+    c = RequestContext(request, {
+        'form': form,
+        'project': project,
+    })
+    return HttpResponse(t.render(c))

=== modified file 'lava_server/htdocs/css/Aristo/jquery-ui-1.8.7.custom.css'
--- lava_server/htdocs/css/Aristo/jquery-ui-1.8.7.custom.css	2011-06-02 22:59:21 +0000
+++ lava_server/htdocs/css/Aristo/jquery-ui-1.8.7.custom.css	2011-09-16 10:02:59 +0000
@@ -56,9 +56,9 @@ 
 
 /* Component containers
 ----------------------------------*/
-.ui-widget { font-family: Helvetica,Arial,sans-serif; font-size: 1.1em; }
+.ui-widget { font-family: Arial,sans-serif; font-size: 1.1em; }
 .ui-widget .ui-widget { font-size: 1em; }
-.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Helvetica,Arial,sans-serif; font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Arial,sans-serif; font-size: 1em; }
 .ui-widget-content { border: 1px solid #B6B6B6; background: #ffffff; color: #4F4F4F; }
 .ui-widget-content a { color: #4F4F4F; }
 .ui-widget-header { border: 1px solid #B6B6B6; color: #4F4F4F; font-weight: bold; }
@@ -98,6 +98,7 @@ 
 	);
 	-webkit-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset;
 	-moz-box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset;
+	box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset;
 }
 .ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #4F4F4F; text-decoration: none; }
 .ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #9D9D9D; font-weight: normal; color: #313131; }
@@ -107,11 +108,11 @@ 
 	color: #1c4257; border: 1px solid #7096ab;
 	background: url(images/bg_fallback.png) 0 -50px repeat-x;
 	background: -webkit-gradient(
-    linear,
-    left bottom,
-    left top,
-    color-stop(1, rgb(185,224,245)),
-    color-stop(0, rgb(146,189,214))
+		linear,
+		left bottom,
+		left top,
+		color-stop(1, rgb(185,224,245)),
+		color-stop(0, rgb(146,189,214))
 	);
 	background: -moz-linear-gradient(
 		center top,
@@ -120,6 +121,7 @@ 
 	);
 	-webkit-box-shadow: none;
 	-moz-box-shadow: none;
+	box-shadow: none;
 }
 .ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #313131; text-decoration: none; }
 .ui-widget :active { outline: none; }
@@ -401,7 +403,7 @@ 
  * http://docs.jquery.com/UI/Autocomplete#theming
  */
 .ui-autocomplete {
-	position: absolute; cursor: default; z-index: 3 !important;
+	position: absolute; cursor: default; z-index: 3;
 	-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
 	-moz-box-shadow: 0 1px 5px rgba(0,0,0,0.3);
 	-webkit-box-shadow: 0 1px 5px rgba(0,0,0,0.3);
@@ -471,33 +473,36 @@ 
 
 /* button animation properties */
 .ui-button {
-    -webkit-transition: all 0.25s ease-in-out;
-    -moz-transition: all 0.25s ease-in-out;
-    -o-transition: all 0.25s ease-in-out;
+    -webkit-transition: -webkit-box-shadow 0.25s ease-in-out;
+    -moz-transition: -moz-box-shadow 0.25s ease-in-out;
+    -o-transition: -o-box-shadow 0.25s ease-in-out;
 }
 
 /*states*/
 .ui-button.ui-state-hover { 
 	-moz-box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset; 
 	-webkit-box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset;
+	box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset;
 }
 .ui-button.ui-state-focus {
 	outline: none;
 	color: #1c4257; border-color: #7096ab;
-	background-image: -webkit-gradient(
-    linear,
-    left bottom,
-    left top,
-    color-stop(1, rgb(185,224,245)),
-    color-stop(0, rgb(146,189,214))
+	background: url(images/bg_fallback.png) 0 -50px repeat-x;
+	background: -webkit-gradient(
+		linear,
+		left bottom,
+		left top,
+		color-stop(1, rgb(185,224,245)),
+		color-stop(0, rgb(146,189,214))
 	);
-	background-image: -moz-linear-gradient(
+	background: -moz-linear-gradient(
 		center top,
 		rgb(185,224,245),
 		rgb(146,189,214)
 	);
-	-webkit-box-shadow: none;
-	-moz-box-shadow: none;
+	-moz-box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset; 
+	-webkit-box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset;
+	box-shadow: 0 0 8px rgba(0, 0, 0, 0.15), 0 1px 0 rgba(255,255,255,0.8) inset;
 }
  
 /*button text element */
@@ -508,7 +513,19 @@ 
 .ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
 .ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
 /* no icon support for input elements, provide padding by default */
-input.ui-button { padding: .4em 1em; }
+input.ui-button { font-size: 14px; font-weight: bold; text-shadow: 0 1px 0 rgba(255, 255, 255, 0.6); padding: 0 1em !important; height: 33px; }
+/*remove submit button internal padding in Firefox*/ 
+input.ui-button::-moz-focus-inner { 
+    border: 0;
+    padding: 0;
+}
+/* fix webkits handling of the box model */
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+	input.ui-button {
+		height: 31px !important;
+	}
+}
+
 
 /*button icon element(s) */
 .ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
@@ -523,11 +540,11 @@ 
 .ui-buttonset .ui-button.ui-state-active { color: #1c4257; border-color: #7096ab; }
 .ui-buttonset .ui-button.ui-state-active {
 	background-image: -webkit-gradient(
-    linear,
-    left bottom,
-    left top,
-    color-stop(1, rgb(185,224,245)),
-    color-stop(0, rgb(146,189,214))
+		linear,
+		left bottom,
+		left top,
+		color-stop(1, rgb(185,224,245)),
+		color-stop(0, rgb(146,189,214))
 	);
 	background-image: -moz-linear-gradient(
 		center top,
@@ -536,6 +553,7 @@ 
 	);
 	-webkit-box-shadow: none;
 	-moz-box-shadow: none;
+	box-shadow: none;
 }
 
 /* workarounds */
@@ -575,12 +593,12 @@ 
  *
  * http://docs.jquery.com/UI/Slider#theming
  */
-.ui-slider { position: relative; text-align: left; background: #d7d7d7; }
-.ui-slider { -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; }
-.ui-slider .ui-slider-handle { background: url(images/slider_handles.png) 0px -23px no-repeat; position: absolute; z-index: 2; width: 23px; height: 23px; cursor: default; border: none; outline: none; -moz-box-shadow: none; -webkit-box-shadow: none; }
+.ui-slider { position: relative; text-align: left; background: #d7d7d7; z-index: 1; }
+.ui-slider { -moz-box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; -webkit-box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; box-shadow: 0 1px 2px rgba(0,0,0,0.5) inset; }
+.ui-slider .ui-slider-handle { background: url(images/slider_handles.png) 0px -23px no-repeat; position: absolute; z-index: 2; width: 23px; height: 23px; cursor: default; border: none; outline: none; -moz-box-shadow: none; -webkit-box-shadow: none; box-shadow: none; }
 .ui-slider  .ui-state-hover, .ui-slider  .ui-state-active { background-position: 0 0; }
 .ui-slider .ui-slider-range { background: #a3cae0; position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
-.ui-slider .ui-slider-range { -moz-box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; -webkit-box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; }
+.ui-slider .ui-slider-range { -moz-box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; -webkit-box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; box-shadow: 0 1px 2px rgba(17,35,45,0.6) inset; }
 
 
 .ui-slider-horizontal { height: 5px; }
@@ -602,7 +620,7 @@ 
  *
  * http://docs.jquery.com/UI/Tabs#theming
  */
-.ui-tabs { position: relative; zoom: 1; border: 0; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs { position: relative; zoom: 1; border: 0; background: transparent; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
 .ui-tabs .ui-tabs-nav { margin: 0; padding: 0; background: transparent; border-width: 0 0 1px 0; }
 .ui-tabs .ui-tabs-nav { 
 	-moz-border-radius: 0;
@@ -615,7 +633,7 @@ 
 .ui-tabs .ui-tabs-nav li.ui-tabs-selected a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-state-processing a { cursor: text; outline: none; }
 .ui-tabs .ui-tabs-nav li a, .ui-tabs.ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-selected a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
 .ui-tabs .ui-tabs-panel { display: block; border-width: 0 1px 1px 1px; padding: 1em 1.4em; background: none; }
-.ui-tabs .ui-tabs-panel { 
+.ui-tabs .ui-tabs-panel { background: #FFF;
 	-moz-border-radius: 0;
 	-webkit-border-radius: 0;
 	border-radius: 0;
@@ -710,7 +728,7 @@ 
 .ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; background: url(images/progress_bar.gif) 0 0 repeat-x; }
 
 /* Extra Input Field Styling */
-textarea, input:not([type="checkbox"]):not([type="radio"]):not([type="file"]) {
+.ui-form textarea, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]) {
 	padding: 3px;
 	-webkit-border-radius: 2px;
 	-moz-border-radius: 2px;
@@ -724,13 +742,13 @@ 
     -moz-transition: all 0.25 ease-in-out;
     -o-transition: all 0.25s ease-in-out;
 }
-textarea:hover, input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):hover {
+.ui-form textarea:hover, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):hover {
 	border: 1px solid #bdbdbd;
 	-webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2);
 	-moz-box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2);
 	box-shadow: 0 1px 3px rgba(0,0,0,0.2) inset, 0 1px 0 rgba(255,255,255,0.2);
 }
-textarea:focus, input:not([type="checkbox"]):not([type="radio"]):not([type="file"]):focus {
+.ui-form textarea:focus, .ui-form input:not([type="submit"]):not([type="button"]):not([type="checkbox"]):not([type="radio"]):not([type="file"]):not([type="range"]):focus {
 	border: 1px solid #95bdd4;
 	-webkit-box-shadow: 0 2px 3px rgba(161,202,226,0.5) inset, 0 1px 0 rgba(255,255,255,0.2);
 	-moz-box-shadow: 0 2px 3px rgba(161,202,226,0.5) inset, 0 1px 0 rgba(255,255,255,0.2);

=== modified file 'lava_server/htdocs/css/default.css'
--- lava_server/htdocs/css/default.css	2011-08-13 00:26:55 +0000
+++ lava_server/htdocs/css/default.css	2011-09-23 08:23:09 +0000
@@ -61,14 +61,31 @@ 
 /* User Box (part of header) */
 
 #lava-server-user-box {
-  text-align: right;
   font-size: 9pt;
+  /* Don't inherit font family, we're moved around and header uses ubuntu light */
+  font-family: "Ubuntu", Helvetica Neue, Verdana, sans-serif !important;
 }
 
 #lava-server-user-box span.username {
   font-weight: bold;
 }
 
+#lava-server-header #lava-server-user-box {
+  /* Float right only when in the user box.
+   * When relocated to the sidebar it will 
+   * Vnot be floating anymore */
+  position: absolute;
+  right: 0;
+  top: 0;
+  padding: 2pt 2pt 5pt 5pt;
+}
+
+#lava-server-sidebar #lava-server-user-box {
+  text-align: right;
+  text-shadow: none;
+}
+
+
 /* Navigation (part of header) */ 
 
 #lava-server-navigation ul,
@@ -126,20 +143,74 @@ 
   content: "";
 }
 
+/* Sidebar */
+
+#lava-server-sidebar {
+}
+
+#lava-server-sidebar.classic {
+  background-color: white;
+  border-radius: 3px;
+  border: 1px solid #999;
+  color: #444;
+  font-family: Ubuntu, Helvetica Neue, Verdana, sans-serif;
+  font-size: 10pt;
+  margin: 1ex;
+  padding: 1ex;
+}
+
+#lava-server-sidebar .lava-sidebar-controls {
+  margin-top: 1em;
+}
+
+#lava-server-sidebar.lava-sidebar {
+  color: black;
+  height: 100%;
+  padding: 2pt 2pt 5pt 5pt;
+  border: none;
+  border-radius: 0;
+  z-index: 1000; /* otherwise data-tables seem to override this */
+  border-left: 1px solid gray;
+  box-shadow: 0 0 0.5em rgba(0, 0, 0, 0.2);
+  background: white;
+  top: 0;
+}
+
+/* Hidden sidebar should be opaque. Removing the border and keeping the shadow
+ * adds a nice dramatic effect. */
+#lava-server-sidebar.lava-sidebar-hidden {
+  opacity: 0.5;
+}
+
+/* Hidden sidebar that users hover should be highlighted nicely */ 
+#lava-server-sidebar.lava-sidebar-hidden:hover {
+  opacity: 1;
+  background: rgba(0, 136, 204, 0.5);
+  text-shadow: 1px 1px 1px #000;
+  color: white !important;
+}
+
+#lava-server-sidebar.lava-sidebar-hidden:hover h1,
+#lava-server-sidebar.lava-sidebar-hidden:hover h2,
+#lava-server-sidebar.lava-sidebar-hidden:hover h3,
+#lava-server-sidebar.lava-sidebar-hidden:hover h4,
+#lava-server-sidebar.lava-sidebar-hidden:hover a {
+  color: inherit !important;
+  border-color: inherit !important;
+}
+
+
 /* Content */
 
 #lava-server-content {
   color: #444;
-  padding: 1ex 3em;
+  padding: 1ex;
   border: 1px solid #999;
-  Border-radius: 3px;
+  border-radius: 3px;
   margin: 1ex;
-  min-width: 800px;
   background-color: white;
 }
 
-#lava-server-sidebar {
-}
 
 h1 {
   color: #222;
@@ -239,3 +310,42 @@ 
   position: relative;
   top: 3px;
 }
+
+/* Form styles */
+table.form_helper {
+  border-collapse: collapse;
+}
+
+table.form_helper th { 
+  vertical-align: top;
+  text-align: right;
+  padding: 1ex;
+}
+
+table.form_helper td {
+  padding: 1ex;
+}
+
+table.form_helper input[type=text] {
+  width: 100%;
+}
+
+table.form_helper p.help_text {
+  margin: 0.2ex 1ex;
+  color: #888;
+}
+
+table.form_helper tr { 
+  border-spacing: 0;
+  padding-top: 1ex;
+}
+
+table.form_helper div.ui-state-error {
+  padding: 1ex;
+}
+
+table.form_helper ul.errorlist {
+  list-style: none;
+  padding: 0;
+  margin: 0;
+}

=== added file 'lava_server/htdocs/css/jquery.lava.sidebar.css'
--- lava_server/htdocs/css/jquery.lava.sidebar.css	1970-01-01 00:00:00 +0000
+++ lava_server/htdocs/css/jquery.lava.sidebar.css	2011-09-22 10:49:23 +0000
@@ -0,0 +1,30 @@ 
+.lava-sidebar {
+  position: fixed;
+  right: 0;
+  width: 30em;
+}
+
+.lava-sidebar-hidden {
+  right: -27em;
+}
+
+.lava-sidebar-hidden * {
+  visibility: hidden;
+}
+
+.lava-sidebar-hidden:hover {
+  cursor: pointer;
+}
+
+.lava-sidebar-controls {
+  text-align: right;
+  border-bottom: 1px dotted gray;
+}
+
+.lava-sidebar-controls .ui-button {
+  width: 32px;
+  height: 32px;
+  margin-left: 3px;
+}
+
+

=== added file 'lava_server/htdocs/js/jquery.cookie.js'
--- lava_server/htdocs/js/jquery.cookie.js	1970-01-01 00:00:00 +0000
+++ lava_server/htdocs/js/jquery.cookie.js	2011-09-22 10:47:47 +0000
@@ -0,0 +1,41 @@ 
+/**
+ * jQuery Cookie plugin
+ *
+ * Copyright (c) 2010 Klaus Hartl (stilbuero.de)
+ * Dual licensed under the MIT and GPL licenses:
+ * http://www.opensource.org/licenses/mit-license.php
+ * http://www.gnu.org/licenses/gpl.html
+ *
+ */
+jQuery.cookie = function (key, value, options) {
+
+    // key and at least value given, set cookie...
+    if (arguments.length > 1 && String(value) !== "[object Object]") {
+        options = jQuery.extend({}, options);
+
+        if (value === null || value === undefined) {
+            options.expires = -1;
+        }
+
+        if (typeof options.expires === 'number') {
+            var days = options.expires, t = options.expires = new Date();
+            t.setDate(t.getDate() + days);
+        }
+
+        value = String(value);
+
+        return (document.cookie = [
+            encodeURIComponent(key), '=',
+            options.raw ? value : encodeURIComponent(value),
+            options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
+            options.path ? '; path=' + options.path : '',
+            options.domain ? '; domain=' + options.domain : '',
+            options.secure ? '; secure' : ''
+        ].join(''));
+    }
+
+    // key and possibly options given, get cookie...
+    options = value || {};
+    var result, decode = options.raw ? function (s) { return s; } : decodeURIComponent;
+    return (result = new RegExp('(?:^|; )' + encodeURIComponent(key) + '=([^;]*)').exec(document.cookie)) ? decode(result[1]) : null;
+};

=== added file 'lava_server/htdocs/js/jquery.lava.sidebar.js'
--- lava_server/htdocs/js/jquery.lava.sidebar.js	1970-01-01 00:00:00 +0000
+++ lava_server/htdocs/js/jquery.lava.sidebar.js	2011-09-22 11:43:08 +0000
@@ -0,0 +1,174 @@ 
+/**
+* LAVA Sidebar plugin for jQuery
+* http://launchpad.net/lava-server/
+* Copyright (c): 2011 Zygmunt Krynicki
+* Licensed under AGPLv3.
+**/
+(function($) {
+  var baseClass = "lava-sidebar";
+  var hiddenClass = "lava-sidebar-hidden";
+
+  $.widget("lava.sidebar", {
+    options: {
+      // hidden by default (unless pinned)
+      hidden: true, 
+      // not pinned by default
+      pinned: false,  
+      pinnedCookieName: "lava-sidebar-pinned",
+      pinnedCookiePath: "/",
+      extraControls: [],
+      onShow: null,
+      onHide: null,
+      onDestroy: null,
+      onPin: null,
+      onUnpin: null,
+    },
+    _pin_button: null,
+    _close_button: null,
+    _controls: null,
+    controls: function() {
+      /* Container of sidebar controls */
+      return this._controls;
+    },
+    _create: function() {
+      /* Constructor */
+      var self = this;
+      if (this.element.html().trim() == "") {
+        // If the sidebar was empty then just hide it
+        this.element.hide();
+      } else {
+        // Load cookie to check for pin status
+        this._load_pin_option();
+        // Initialize basic stuff:
+        // 1) css class
+        // 2) click handler
+        this.element
+          .addClass(baseClass)
+          .bind("click.sidebar", function(event) {
+            if (self.element.hasClass(hiddenClass)) {
+              self.show();
+              event.preventDefault();
+              event.stopPropagation();
+            }
+          });
+        // Initialize controls bar
+        self._controls = $("<div/>", {
+            "class": "lava-sidebar-controls"
+          })
+          .prependTo(this.element);
+        // Initialize pin button
+        self._pin_button = $("<span/>", {
+            title: "Pin/unpin the sidebar"
+          })
+        .button({
+            text: false,
+            icons: { primary: this.options.pinned ? 'ui-icon-pin-s' : 'ui-icon-pin-w'}
+          })
+          .click(function(event) {
+            self.element.sidebar("option", "pinned", !self.options.pinned);
+          })
+          .appendTo(self._controls);
+        // Initialize close button
+        self._close_button = $("<span/>", {
+            title: "Collapse the sidebar"
+          })
+          .button({
+            text: false,
+            icons: { primary: 'ui-icon-circle-triangle-e'}
+          })
+          .bind("click", function(event) {
+            self.hide(); 
+            event.preventDefault();
+            event.stopPropagation();
+          })
+          .appendTo(self._controls);
+        $.each(self.options.extraControls, function(index, control) {
+          control.appendTo(self._controls);
+        });
+      }
+    },
+    _init: function() {
+      /* Initializer */
+      if (this.options.pinned == false && this.options.hidden == true) {
+        this.hide();
+      } else {
+        this.show();
+      }
+    },
+    destroy: function() {
+      /* Destructor */
+      this.element.removeClass(baseClass);
+      this.element.removeClass(hiddenClass);
+      this.element.unbind("click.sidebar");
+      this._controls.remove();
+      $.Widget.prototype.destroy.call(this);
+      if (this.options.onDestroy) {
+        this.options.onDestroy();
+      }
+    },
+    show: function() {
+      /* Show the sidebar */
+      this.element.removeClass(hiddenClass);
+      this.options.hidden = false;
+      if (this.options.onShow) {
+        this.options.onShow();
+      }
+    },
+    hide: function() {
+      /* Hide the sidebar */
+      this.element.addClass(hiddenClass);
+      this.options.hidden = true;
+      if (this.options.onHide) {
+        this.options.onHide();
+      }
+    },
+    pin: function() {
+      /* Pin the sidebar so that it is shown by default */
+      this._set_pinned(true);
+    },
+    unpin: function() {
+      /* Unpin the sidebar */
+      this._set_pinned(false);
+    },
+    _set_pinned: function(pin_state) {
+      this.options.pinned = (pin_state == true);
+      this._pin_button.button("option", "icons", { primary: this.options.pinned ? 'ui-icon-pin-s' : 'ui-icon-pin-w'});
+      this._save_pin_option();
+      if (this.options.pinned == true && this.options.onPin) {
+        this.options.onPin();
+      }
+      if (this.options.pinned == false && this.options.onUnpin) {
+        this.options.onUnpin();
+      }
+    },
+    _load_pin_option: function() {
+      this.options.pinned = $.cookie(
+        this.options.pinnedCookieName, {
+          path: this.optionsPinnedCookiePath,
+          expires: 365
+        }) == "pinned";
+    },
+    _save_pin_option: function() {
+      $.cookie(
+        this.options.pinnedCookieName,
+        this.options.pinned ? "pinned" : "", {
+          path: this.options.pinnedCookiePath,
+          expires: 365
+        });
+    },
+    _setOption: function(key, value) {
+      /* jQueryUI widget method for implementing option setter */
+      $.Widget.prototype._setOption.apply(this, arguments);
+      if (key == "hidden") {
+        if (value) {
+          this.hide();
+        } else {
+          this.show();
+        }
+      }
+      if (key == "pinned") {
+        this._set_pinned(value);
+      }
+    }
+  });
+})(jQuery);

=== added file 'lava_server/htdocs/js/jquery.slugify.js'
--- lava_server/htdocs/js/jquery.slugify.js	1970-01-01 00:00:00 +0000
+++ lava_server/htdocs/js/jquery.slugify.js	2011-09-23 08:21:23 +0000
@@ -0,0 +1,58 @@ 
+/*
+ * Taken from git://github.com/pmcelhaney/jQuery-Slugify-Plugin.git
+ */
+(function ($) {
+
+	$.fn.slugify = function (source, options) {
+		var $target = this;
+		var $source = $(source);
+
+		var settings = $.extend({
+			slugFunc: (function (val, originalFunc) { return originalFunc(val); })
+		}, options);
+
+
+		var convertToSlug = function(val) {
+			return settings.slugFunc(val, 
+				(function(v) {
+					if (!v) return '';
+          var from = "ıİöÖüÜçÇğĞşŞâÂêÊîÎôÔûÛĘęÓ󥹌śŁłŻżŹźĆćŃń";
+					var to   = "iIoOuUcCgGsSaAeEiIoOuUEeOoAaSsLlZzZzCcNn";
+					
+					for (var i=0, l=from.length ; i<l ; i++) {
+					    v = v.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
+					}
+
+					return v.replace(/'/g, '').replace(/\s*&\s*/g, ' and ').replace(/[^A-Za-z0-9]+/g, '-').replace(/^-|-$/g, '').toLowerCase();
+				})  
+			);
+		}
+
+		var setLock = function () {
+			if($target.val() != null && $target.val() != '') {
+				$target.addClass('slugify-locked');
+			} else {
+				$target.removeClass('slugify-locked');
+			}
+		}
+
+		var updateSlug = function () {
+			var slug = convertToSlug($(this).val());
+			$target.filter(':not(.slugify-locked)').val(slug).text(slug);		
+		}
+
+
+		$source.keyup( updateSlug ).change( updateSlug ); 
+
+		$target.change(function () {       
+			var slug = convertToSlug($(this).val());
+			$target.val(slug).text(slug);
+			setLock();
+		});   
+
+		setLock();         
+
+		return this; 
+	};
+    
+})(jQuery);             

=== modified file 'lava_server/manage.py'
--- lava_server/manage.py	2011-09-07 16:39:04 +0000
+++ lava_server/manage.py	2011-09-16 09:40:08 +0000
@@ -108,3 +108,16 @@ 
 def main():
     find_sources()
     run_with_dispatcher_class(LAVAServerDispatcher)
+
+
+def legacy_main():
+    find_sources()
+    settings_module = "lava_server.settings.development"
+    settings = __import__(settings_module, fromlist=[''])
+    from django.core.management import execute_manager
+    execute_manager(settings) 
+
+
+if __name__ == "__main__":
+    legacy_main()
+    

=== modified file 'lava_server/settings/common.py'
--- lava_server/settings/common.py	2011-07-21 23:11:25 +0000
+++ lava_server/settings/common.py	2011-09-23 08:25:04 +0000
@@ -91,6 +91,7 @@ 
     # 'django.contrib.admindocs',
     # Admin docs disabled due to: https://code.djangoproject.com/ticket/6681
     'linaro_django_xmlrpc',
+    'lava_markitup',  # Support app for MarkItUp in LAVA
     'south',
 ]
 

=== modified file 'lava_server/settings/development.py'
--- lava_server/settings/development.py	2011-07-20 20:30:04 +0000
+++ lava_server/settings/development.py	2011-09-16 09:40:08 +0000
@@ -28,20 +28,8 @@ 
 # uses virtualenv and look for a variable virtualenv injects into the
 # environment.
 LOCALENV_DIR = os.getenv("VIRTUAL_ENV")
-if LOCALENV_DIR is None:
-    raise ImproperlyConfigured("Development mode REQUIRES VIRTUAL_ENV to be set")
-
-# Top-level directory for nonvolatile files, as used by lava-dev-tool. It is a
-# sibling directory to localenv so it's easier to define its location as
-# relative to LOCALENV_DIR.
-PRECIOUS_DIR = os.path.join(LOCALENV_DIR, "../precious")
-
-# Top-level directory of the precious project state.
-#
-# In short: this is where your non-source content ends up at, this place should
-# keep the database file(s), user uploaded media files as well as the cache of
-# static files, if built.
-PROJECT_STATE_DIR = os.path.join(PRECIOUS_DIR, "var/lib/lava-server/")
+if not LOCALENV_DIR:
+    print "Not using lava-dev-tool? Well okay, but just be careful with your imports and python path"
 
 # Top-level directory of the project.
 #
@@ -54,6 +42,29 @@ 
             os.path.abspath(__file__)),
         "..")) 
 
+# Top-level directory for nonvolatile files, as used by lava-dev-tool. It is a
+# sibling directory to localenv so it's easier to define its location as
+# relative to LOCALENV_DIR.
+if LOCALENV_DIR:
+    PRECIOUS_DIR = os.path.join(LOCALENV_DIR, "../precious")
+else:
+    PRECIOUS_DIR = os.path.join(PROJECT_SRC_DIR, "precious") 
+
+# Create precious directory if needed
+if not os.path.exists(PRECIOUS_DIR):
+    os.makedirs(PRECIOUS_DIR)
+
+# Top-level directory of the precious project state.
+#
+# In short: this is where your non-source content ends up at, this place should
+# keep the database file(s), user uploaded media files as well as the cache of
+# static files, if built.
+PROJECT_STATE_DIR = os.path.join(PRECIOUS_DIR, "var/lib/lava-server/")
+
+# Create state directory if needed
+if not os.path.exists(PROJECT_STATE_DIR):
+    os.makedirs(PROJECT_STATE_DIR)
+
 DEBUG = True
 DEBUG_PROPAGATE_EXCEPTIONS = True
 TEMPLATE_DEBUG = DEBUG

=== modified file 'lava_server/templates/index.html'
--- lava_server/templates/index.html	2011-08-18 15:00:38 +0000
+++ lava_server/templates/index.html	2011-09-16 12:59:19 +0000
@@ -1,4 +1,4 @@ 
-{% extends "layouts/content.html" %}
+{% extends "layouts/content_with_sidebar.html" %}
 {% load i18n %}
 
 

=== modified file 'lava_server/templates/layouts/base.html'
--- lava_server/templates/layouts/base.html	2011-08-18 15:00:57 +0000
+++ lava_server/templates/layouts/base.html	2011-09-22 10:50:10 +0000
@@ -6,8 +6,11 @@ 
     {% block scripts_and_styles %}
     <script type="text/javascript" src="{{ STATIC_URL }}lava-server/js/jquery-1.5.1.min.js"></script> 
     <script type="text/javascript" src="{{ STATIC_URL }}lava-server/js/jquery-ui-1.8.12.custom.min.js"></script> 
+    <script type="text/javascript" src="{{ STATIC_URL }}lava-server/js/jquery.cookie.js"></script>
+    <script type="text/javascript" src="{{ STATIC_URL }}lava-server/js/jquery.lava.sidebar.js"></script>
     <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava-server/css/Aristo/jquery-ui-1.8.7.custom.css"/>
     <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava-server/css/default.css"/>
+    <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava-server/css/jquery.lava.sidebar.css"/>
     {% endblock %}
     {% block extrahead %}{% endblock %}
   </head>

=== modified file 'lava_server/templates/layouts/content_with_sidebar.html'
--- lava_server/templates/layouts/content_with_sidebar.html	2011-07-25 23:18:01 +0000
+++ lava_server/templates/layouts/content_with_sidebar.html	2011-09-22 10:50:10 +0000
@@ -3,15 +3,113 @@ 
 
 {% block content-and-sidebar %}
 <div id="lava-server-content-and-sidebar">
+  <!-- sidebar -->
+  <div id="lava-server-sidebar" class="classic"> 
+    {% block sidebar %}{% endblock %}
+  </div>
   <!-- content -->
   <div id="lava-server-content">
-    <!-- sidebar -->
-    <div id="lava-server-sidebar"> 
-      {% block sidebar %}{% endblock %}
-    </div>
     <!-- !sidebar -->
     {% block content %}{% endblock %}
   </div>
   <!-- !content -->
 </div>
+<script type="text/javascript">
+  $(window).load(function() {
+    var preferDockedCookieName = "lava-server-prefer-docked";
+    var sidebar = $("#lava-server-sidebar");
+    var dock = $("<span/>")
+    .button({
+      label: "Dock the sidebar",
+      icons: { secondary: "ui-icon-circle-triangle-e" }
+    })
+    .css("float", "right")
+    .prependTo(sidebar)
+    .click(function() {
+      dock_the_sidebar();
+    });
+
+    function dock_the_sidebar() {
+      var user_box = $("#lava-server-user-box");
+      var content = $("#lava-server-content");
+      var header = $("#lava-server-header");
+      // Compute how much margin should remain once the sidebar is toggled
+      var base_margin = (
+        $("#lava-server-content").outerWidth(true) 
+        - $("#lava-server-content").outerWidth(false)) / 2;
+      // Remove the 'try new sidebar' button
+      dock.hide();
+
+      // As for the sidebar
+      sidebar
+        // Remove the 'classic' class
+        .removeClass("classic")
+        // Enable the new looks
+        .sidebar({
+          onShow: function() {
+            user_box.prependTo(sidebar);
+            content.css("marginRight", base_margin + sidebar.outerWidth(true) + "px");
+          },
+          onHide: function() {
+            user_box.prependTo(header);
+            content.css("marginRight", base_margin + "px");
+          },
+          onDestroy: function() {
+            dock.show();
+            user_box.prependTo(header);
+            content.css("marginRight", base_margin + "px");
+            sidebar.addClass("classic")
+          },
+          onPin: function() {
+            var heart = $("#heart");
+            if (!heart.attr("checked")) {
+              heart.attr("checked", "checked");
+              heart.change();  // jQuery does not seem to fire this manually.
+              heart.parent("div").effect("highlight", {}, 3000);
+            }
+          },
+          extraControls: [
+            // Undock button
+            $("<span/>", {
+              title: "Undock the sidebar"
+            })
+            .css("float", "left")
+            .button({
+              label: "Undock",
+              text: false,
+              icons: { primary: 'ui-icon-circle-triangle-w' }
+            })
+            .click(function () { sidebar.sidebar("destroy"); }),
+            // Preferences
+            $('<div/>')
+            .css({
+              textAlign: "left",
+              marginTop: "1ex"
+            })
+            .append(
+              $('<input/>', {
+                type: "checkbox",
+                checked: $.cookie(preferDockedCookieName, {path: '/'}) == 'true',
+                id: "heart",
+                title: "Dock the sidebar automatically"
+              })
+              .change(function() {
+                $.cookie(preferDockedCookieName, $(this).attr("checked") ? true : '', {path: '/'})
+              })
+            )
+            .append(
+              $('<label/>', { "for": "heart" })
+              .append($("<span/>")
+                .text("Dock the sidebar automatically")
+              )
+            ),
+          ]
+        });
+    }
+
+    if ($.cookie(preferDockedCookieName) == "true") {
+      dock_the_sidebar();
+    }
+  });
+</script>
 {% endblock %}

=== modified file 'lava_server/templates/linaro_django_xmlrpc/_base.html'
--- lava_server/templates/linaro_django_xmlrpc/_base.html	2011-08-18 15:02:02 +0000
+++ lava_server/templates/linaro_django_xmlrpc/_base.html	2011-09-16 09:46:32 +0000
@@ -1,4 +1,4 @@ 
-{% extends "layouts/content.html" %}
+{% extends "layouts/content_with_sidebar.html" %}
 {% load i18n %}
 
 

=== modified file 'lava_server/urls.py'
--- lava_server/urls.py	2011-07-13 12:48:44 +0000
+++ lava_server/urls.py	2011-09-23 08:25:04 +0000
@@ -52,6 +52,7 @@ 
     url(r'^' + settings.APP_URL_PREFIX + r'api/', include(api_urls.token_urlpatterns)),
     # XXX: This is not needed but without it linaro-django-xmlrpc tests fail
     url(r'^' + settings.APP_URL_PREFIX + r'api/', include(api_urls.default_mapper_urlpatterns)),
+    url(r'^' + settings.APP_URL_PREFIX + r'utils/markitup/', include('lava_markitup.urls')),
 )
 
 

=== modified file 'setup.py'
--- setup.py	2011-09-05 12:51:34 +0000
+++ setup.py	2011-09-23 08:35:26 +0000
@@ -32,6 +32,8 @@ 
         lava-server = lava_server.manage:main
         [lava_server.commands]
         manage=lava_server.manage:manage
+        [lava_server.extensions]
+        project=lava_projects.extension:ProjectExtension
     """,
     test_suite="lava_server.tests.run_tests",
     license="AGPL",
@@ -44,12 +46,13 @@ 
     """,
     url='https://launchpad.net/lava-server',
     classifiers=[
-        "Development Status :: 3 - Alpha",
+        "Development Status :: 4 - Beta",
         "Intended Audience :: Developers",
         "License :: OSI Approved :: GNU Affero General Public License v3",
         "License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL)",
         "Operating System :: OS Independent",
         "Programming Language :: Python :: 2.6",
+        "Programming Language :: Python :: 2.7",
         "Topic :: Software Development :: Testing",
     ],
     install_requires=[
@@ -61,6 +64,7 @@ 
         'python-openid >= 2.2.4',  # this should be a part of django-openid-auth deps
         'south >= 0.7.3',
         'versiontools >= 1.3.1',
+        'markdown >= 2.0.3',
     ],
     setup_requires=[
         'versiontools >= 1.3.1',