From patchwork Mon Sep 26 09:01:15 2011 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Zygmunt Krynicki X-Patchwork-Id: 4328 Return-Path: X-Original-To: patchwork@peony.canonical.com Delivered-To: patchwork@peony.canonical.com Received: from fiordland.canonical.com (fiordland.canonical.com [91.189.94.145]) by peony.canonical.com (Postfix) with ESMTP id 6B60E23EF9 for ; Mon, 26 Sep 2011 09:08:25 +0000 (UTC) Received: from mail-fx0-f52.google.com (mail-fx0-f52.google.com [209.85.161.52]) by fiordland.canonical.com (Postfix) with ESMTP id 445F1A189A3 for ; Mon, 26 Sep 2011 09:08:25 +0000 (UTC) Received: by fxe23 with SMTP id 23so8043403fxe.11 for ; Mon, 26 Sep 2011 02:08:25 -0700 (PDT) Received: by 10.223.30.151 with SMTP id u23mr858466fac.122.1317028105037; Mon, 26 Sep 2011 02:08:25 -0700 (PDT) X-Forwarded-To: linaro-patchwork@canonical.com X-Forwarded-For: patch@linaro.org linaro-patchwork@canonical.com Delivered-To: patches@linaro.org Received: by 10.152.3.234 with SMTP id f10cs30686laf; Mon, 26 Sep 2011 02:08:24 -0700 (PDT) Received: by 10.216.172.198 with SMTP id t48mr8559816wel.79.1317028103884; Mon, 26 Sep 2011 02:08:23 -0700 (PDT) Received: from indium.canonical.com (indium.canonical.com. [91.189.90.7]) by mx.google.com with ESMTPS id p36si6625415weq.23.2011.09.26.02.08.23 (version=TLSv1/SSLv3 cipher=OTHER); Mon, 26 Sep 2011 02:08:23 -0700 (PDT) Received-SPF: pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) client-ip=91.189.90.7; Authentication-Results: mx.google.com; spf=pass (google.com: best guess record for domain of bounces@canonical.com designates 91.189.90.7 as permitted sender) smtp.mail=bounces@canonical.com Received: from ackee.canonical.com ([91.189.89.26]) by indium.canonical.com with esmtp (Exim 4.71 #1 (Debian)) id 1R87Ak-0003lI-W2 for ; Mon, 26 Sep 2011 09:08:23 +0000 Received: from ackee.canonical.com (localhost [127.0.0.1]) by ackee.canonical.com (Postfix) with ESMTP id C4BE4E2191 for ; Mon, 26 Sep 2011 09:01:15 +0000 (UTC) MIME-Version: 1.0 X-Launchpad-Project: lava-server X-Launchpad-Branch: ~linaro-validation/lava-server/trunk X-Launchpad-Message-Rationale: Subscriber X-Launchpad-Branch-Revision-Number: 238 X-Launchpad-Notification-Type: branch-revision To: Linaro Patch Tracker From: noreply@launchpad.net Subject: [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> Date: Mon, 26 Sep 2011 09:01:15 -0000 Reply-To: noreply@launchpad.net Sender: bounces@canonical.com Errors-To: bounces@canonical.com Precedence: bulk X-Generated-By: Launchpad (canonical.com); Revision="14012"; Instance="launchpad-lazr.conf" X-Launchpad-Hash: 8d7cc8f8d333a5948604271bde5361e64605585e 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 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 === 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('
'); + $$.wrap('
'); + $$.wrap('
'); + $$.addClass("markItUpEditor"); + + // add the header before the textarea + header = $('
').insertBefore($$); + $(dropMenus(options.markupSet)).appendTo(header); + + // add the footer after the textarea + footer = $('
').insertAfter($$); + + // add the resize handle after textarea + if (options.resizeHandle === true && $.browser.safari !== true) { + resizeHandle = $('
') + .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 = $('
    '), 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 = $('
  • '+(button.separator||'')+'
  • ').appendTo(ul); + } else { + i++; + for (j = levels.length -1; j >= 0; j--) { + t += levels[j]+"-"; + } + li = $('
  • '+(button.name||'')+'
  • ') + .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 = $(''); + 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(//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 @@ + + + + +markItUp! preview template + + + + + + === 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 @@ + + + + + Preview + + + + {{ markup|safe }} + + + === 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 +# +# 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 . + +__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 +# +# 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 . + +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 +# +# 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 . + +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 +# +# 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 . + +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 +# +# 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 . + +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 @@ +

    Actions

    + === 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 }} + + + + + +{% endblock %} + + +{% block content %} + +
    + {% block form_header %} + {% endblock %} + {% csrf_token %} + + {% for field in form %} + {% block form_field %} + + + + + {% endblock %} + {% endfor %} +
    + {% if field.errors %} +
    + {{ field.errors }} + {{ field }} +
    + {% else %} + {{ field }} + {% endif %} +

    {{ field.help_text }}

    +
    + {% block form_footer %} +

    Continue

    + + {% endblock %} +
    +{% 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 %} +

    Project administration

    + +{% endif %} +{% endblock %} + + +{% block content %} +{% if former_identifier %} +
    +

    + This project was renamed to {{ project.identifier }} + {{ former_identifier.renamed_on|timesince }} ago. The old URL referring to + {{ former_identifier.former_identifier }} will redirect here + automatically but you should update your bookmarks or hyper-links

    +
    +{% endif %} +

    {{ project }}

    +

    +{% 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 %} +(you are a member of this group) +{% endif %} +, registered by {{ project.registered_by }} on {{ project.registered_on|date }} +{% endif %} +

    +
    + {{ project.description|markdown }} +
    +{% 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 %} +

    Existing projects

    +
      + {% for project in project_list %} +
    • + {{ project }} + {% if not project.is_public %}(private){% endif %} +
    • + {% endfor %} +
    +{% 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 %} +

    Register a project in LAVA

    +{% endblock %} + + +{% block form_footer %} +{{ block.super }} + +{% 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 %} +

    Rename {{ project }}

    +

    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.

    +{% endblock %} + + +{% block form_field %} +{% if field.name == "identifier" %} + + Old identifier + + +

    This is your current project identifier

    + + +{% endif %} +{{ block.super }} +{% endblock %} + + +{% block form_footer %} +

    Continue

    + or cancel + +{% 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 %} +

    Projects

    +

    LAVA uses projects to organize other data structures. Projects are similar +to the project concept in other development tools and web applications.

    + +

    Anyone is free to register a + project and use that to coordinate testing efforts.

    + +{% if recent_project_list %} +

    Recently registered projects

    +
      + {% for project in recent_project_list %} +
    • + {{ project }}, + registered {{ project.registered_on|timesince }} ago + {% if not project.is_public %}(private){% endif %} +
    • + {% endfor %} +
    +

    See all projects

    +{% 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 %} +

    Update {{ project }}

    +{% endblock %} + + +{% block form_field %} +{{ block.super }} +{% if field.name == "name" %} + + Identifier
    (read only) + + +

    + Rename this project if you wish to change the identifier

    + + +{% endif %} +{% endblock %} + + +{% block form_footer %} +

    Continue

    + or cancel + +{% 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 +# +# 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 . + +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[a-z0-9-]+)/$', 'project_detail', name='lava.project.detail'), + url(r'^(?P[a-z0-9-]+)/\+update/$', 'project_update', name='lava.project.update'), + url(r'^(?P[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 +# +# 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 . + +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 = $("
    ", { + "class": "lava-sidebar-controls" + }) + .prependTo(this.element); + // Initialize pin button + self._pin_button = $("", { + 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 = $("", { + 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 + + + {% endblock %} {% block extrahead %}{% endblock %} === 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 %}
    + +
    + {% block sidebar %}{% endblock %} +
    - -
    - {% block sidebar %}{% endblock %} -
    {% block content %}{% endblock %}
    + {% 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',