=== added file 'dashboard_app/forms.py'
--- dashboard_app/forms.py	1970-01-01 00:00:00 +0000
+++ dashboard_app/forms.py	2011-10-06 12:03:00 +0000
@@ -0,0 +1,26 @@
+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
+from dashboard_app.models import TestingEffort
+
+
+class TestingEffortForm(forms.Form):
+
+    name = forms.CharField(
+        label=_(u"Name"),
+        max_length=100)
+
+    description = forms.CharField(
+        required=False,
+        widget=forms.widgets.Textarea(),
+        label=_(u"Description"),
+        help_text=_(u"Description of this testing effort"))
+
+    tags = forms.CharField(
+        required=False,
+        label=_(u"Tags"),
+        max_length=1024,
+        help_text=_(u"Tags, separated by whitespace or commas"))

=== modified file 'dashboard_app/templates/dashboard_app/testing_effort_detail.html'
--- dashboard_app/templates/dashboard_app/testing_effort_detail.html	2011-09-28 02:30:30 +0000
+++ dashboard_app/templates/dashboard_app/testing_effort_detail.html	2011-10-06 12:03:00 +0000
@@ -14,8 +14,17 @@
 
 
 {% block sidebar %}
-<h3>{{ effort }}</h3>
-{{ effort.description|markdown }}
+<h3>Administration</h3>
+{% if belongs_to_user %}
+<ul>
+  <li><a href="{% url dashboard_app.views.testing_effort_update effort.pk %}">Update this effort</a>
+</ul>
+{% else %}
+<p>You cannot make any changes to this testing effort. Only the owner of the
+<a href="{{ effort.project.get_absolute_url }}">{{ effort.project }}</a> can do
+this.</p>
+{% endif %}
+
 <h3>Tags</h3>
 <p class="help_text">The concept of <q>testing efforts</q> is based on using
 tags to associate test runs with a common goal or task. This testing effort
@@ -67,6 +76,10 @@
     padding: 2pt;
   }
 </style>
+<h2>About {{ effort.name }}</h2>
+{{ effort.description|markdown }}
+<h2>Tests related to this effort</h2>
+<p>The following tests contain one of the tags associated with this effort</p>
 <table class="special">
   {% regroup test_run_list|dictsortreversed:"analyzer_assigned_date" by analyzer_assigned_date|date:"Y-m-d" as test_run_cluster_list %}
   {% for test_run_cluster in test_run_cluster_list %}

=== added file 'dashboard_app/templates/dashboard_app/testing_effort_form.html'
--- dashboard_app/templates/dashboard_app/testing_effort_form.html	1970-01-01 00:00:00 +0000
+++ dashboard_app/templates/dashboard_app/testing_effort_form.html	2011-10-06 12:03:00 +0000
@@ -0,0 +1,61 @@
+{% extends "dashboard_app/_content_with_sidebar.html" %}
+{% comment %}
+This template is a candidate for lava-server/form.html (generic form template)
+To make it work sensibly we need to move javascript and theme code to widgets
+and start using proper widget media references
+{% endcomment %}
+{% load humanize %}
+{% load markup %}
+{% load i18n %}
+
+
+{% block extrahead %}
+{{ block.super }}
+<script type="text/javascript" src="{{ STATIC_URL }}lava_markitup/jquery.markitup.js"></script>
+<script type="text/javascript" src="{{ STATIC_URL }}lava_markitup/sets/markdown/set.js"></script>
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava_markitup/skins/simple/style.css"/>
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}lava_markitup/sets/markdown/style.css"/>
+{% endblock %}
+
+
+{% block content %}
+<style type="text/css">
+  iframe { border: 1px dotted #333; }
+</style>
+<form method="post" action="">
+  {% block form_header %}
+  {% endblock %}
+  {% csrf_token %}
+  <table class="form_helper">
+    {% for field in form %}
+    {% block form_field %}
+    <tr>
+      <th><label for="{{ field.id_for_label }}">{{ field.label }}</label></th>
+      <td>
+        {% if field.errors %}
+        <div class="ui-state-error">
+          {{ field.errors }}
+          {{ field }}
+        </div>
+        {% else %}
+        {{ field }}
+        {% endif %}
+        <p class='help_text'>{{ field.help_text }}</p>
+      </td>
+    </tr>
+    {% endblock %}
+    {% endfor %}
+  </table>
+  {% block form_footer %}
+  <h2>Continue</h2>
+  <input type="submit" value="Save"/>
+  {% endblock %}
+</form>
+<script type="text/javascript">
+  mySettings.previewParserPath = "{% url lava.markitup.markdown %}";
+  $(document).ready(function() {
+    $("input[type=submit]").button();
+    $("#id_description").markItUp(mySettings);
+  });
+</script>
+{% endblock %}

=== modified file 'dashboard_app/urls.py'
--- dashboard_app/urls.py	2011-09-29 13:06:54 +0000
+++ dashboard_app/urls.py	2011-10-06 12:03:00 +0000
@@ -67,4 +67,6 @@
     url(r'^image_status/(?P<rootfs_type>[a-zA-Z0-9_-]+)\+(?P<hwpack_type>[a-zA-Z0-9_-]+)/test-history/(?P<test_id>[^/]+)/$', 'image_test_history'),
     url(r'^efforts/$', 'testing_effort_list'),
     url(r'^efforts/(?P<pk>[0-9]+)/$', 'testing_effort_detail'),
+    url(r'^efforts/(?P<pk>[0-9]+)/update/$', 'testing_effort_update'),
+    url(r'^efforts/(?P<project_identifier>[a-z0-9-]+)/\+new/$', 'testing_effort_create'),
 )

=== modified file 'dashboard_app/views.py'
--- dashboard_app/views.py	2011-09-28 02:30:30 +0000
+++ dashboard_app/views.py	2011-10-06 12:03:00 +0000
@@ -20,14 +20,17 @@
 Views for the Dashboard application
 """
 
+import re
 import json
 
+from django.contrib.auth.decorators import login_required
 from django.contrib.sites.models import Site
 from django.db.models.manager import Manager
 from django.db.models.query import QuerySet
-from django.http import Http404, HttpResponse
+from django.http import Http404, HttpResponse, HttpResponseRedirect
 from django.shortcuts import render_to_response, redirect, get_object_or_404
-from django.template import RequestContext
+from django.template import RequestContext, loader
+from django.views.generic.create_update import create_object 
 from django.views.generic.list_detail import object_list, object_detail
 
 from dashboard_app.models import (
@@ -37,12 +40,16 @@
     DataReport,
     DataView,
     ImageHealth,
+    Tag,
     Test,
     TestResult,
     TestRun,
     TestingEffort,
 )
-from dashboard_app.bread_crumbs import BreadCrumb, BreadCrumbTrail
+from lava_server.bread_crumbs import (
+    BreadCrumb,
+    BreadCrumbTrail,
+)
 
 
 def _get_queryset(klass):
@@ -594,6 +601,7 @@
     return render_to_response(
         "dashboard_app/testing_effort_detail.html", {
             'effort': effort,
+            'belongs_to_user': effort.project.is_owned_by(request.user),
             'test_run_list': effort.get_test_runs(
             ).select_related(
                 'denormalization',
@@ -606,3 +614,92 @@
                 effort=effort,
                 pk=pk),
         }, RequestContext(request))
+
+
+from lava_projects.models import Project
+from lava_projects.views import project_detail 
+from dashboard_app.forms import TestingEffortForm
+
+
+@BreadCrumb(
+    "Start a new test effort",
+    parent=project_detail,
+    needs=["project_identifier"])
+@login_required
+def testing_effort_create(request, project_identifier):
+    project = get_object_or_404(Project, identifier=project_identifier)
+    if request.method == 'POST':
+        form = TestingEffortForm(request.POST)
+        # Check the form
+        if form.is_valid():
+            # And make a project instance
+            effort = TestingEffort.objects.create(
+                name=form.cleaned_data['name'],
+                description=form.cleaned_data['description'],
+                project=project)
+            # Create all the required tags
+            effort.tags = [
+                Tag.objects.get_or_create(name=tag_name)[0]
+                for tag_name in re.split("[, ]+", form.cleaned_data["tags"])
+                if tag_name != ""]
+            return HttpResponseRedirect(effort.get_absolute_url())
+    else:
+        form = TestingEffortForm()
+    # Render to template
+    template_name = "dashboard_app/testing_effort_form.html"
+    t = loader.get_template(template_name)
+    c = RequestContext(request, {
+        'form': form,
+        'bread_crumb_trail': BreadCrumbTrail.leading_to(
+            testing_effort_create,
+            project=project,
+            project_identifier=project.identifier)
+    })
+    return HttpResponse(t.render(c))
+
+
+@BreadCrumb(
+    "Update",
+    parent=testing_effort_detail,
+    needs=["pk"])
+@login_required
+def testing_effort_update(request, pk): 
+    try:
+        effort = TestingEffort.objects.get(pk=pk)
+    except TestingEffort.DoesNotExist:
+        raise Http404()
+    if not effort.project.is_owned_by(request.user):
+        return HttpReseponse("not allowed")
+    if request.method == 'POST':
+        form = TestingEffortForm(request.POST)
+        # Check the form
+        if form.is_valid():
+            # And update the effort object
+            effort.name=form.cleaned_data['name']
+            effort.description=form.cleaned_data['description']
+            # As well as tags
+            effort.tags = [
+                Tag.objects.get_or_create(name=tag_name)[0]
+                for tag_name in re.split("[, ]+", form.cleaned_data["tags"])
+                if tag_name != ""]
+            # Save the changes
+            effort.save()
+            return HttpResponseRedirect(effort.get_absolute_url())
+    else:
+        form = TestingEffortForm(initial={
+            'name': effort.name,
+            'description': effort.description,
+            'tags': " ".join([tag.name for tag in effort.tags.order_by('name').all()])
+        })
+    # Render to template
+    template_name = "dashboard_app/testing_effort_form.html"
+    t = loader.get_template(template_name)
+    c = RequestContext(request, {
+        'form': form,
+        'effort': effort,
+        'bread_crumb_trail': BreadCrumbTrail.leading_to(
+            testing_effort_update,
+            effort=effort,
+            pk=effort.pk)
+    })
+    return HttpResponse(t.render(c))

