diff --git a/README.rst b/README.rst
index 2a7cea5b..19589abd 100644
--- a/README.rst
+++ b/README.rst
@@ -15,6 +15,12 @@ investing more into developing it.
Current features
----------------
+*Project views*
+ Basic project views that let you retrieve the list of tasks for a given
+ project, as well as an example of a workflow-oriented view (the 'Triage
+ bugs' view). The current POC is is also just a minimal stub of the project
+ view feature set.
+
*Bug tracking*
Like Launchpad Bugs, StoryBoard implements bugs as stories, with tasks that
may affect various project/branch combinations. You can currently create
@@ -23,11 +29,18 @@ Current features
and is missing search features, pagination, results ordering. This should
definitely be improved if we go forward with this.
-*Project views*
- Basic project views that let you retrieve the list of tasks for a given
- project, as well as an example of a workflow-oriented view (the 'Triage
- bugs' view). The current POC is is also just a minimal stub of the project
- view feature set.
+
+*Feature tracking*
+ The equivalent of Launchpad Blueprints, they inherit the same 'story'
+ framework as bugs. That means they don't have most of the limitations of
+ LP blueprints: you can comment in them, you can have tasks affecting multiple
+ projects, you can even have multiple tasks affecting the same project and
+ order them !
+
+*Project groups*
+ Projects can be grouped together arbitrarily, and all 'project' views can
+ be reused by project groups. That makes it easy to triage or track all
+ tasks for projects within a given OpenStack program.
*Markdown descriptions and comments*
Story descriptions and comments can use markdown for richer interaction.
@@ -52,18 +65,6 @@ No invalid/wontfix/opinion status
Future features
---------------
-*Feature tracking*
- The equivalent of Launchpad Blueprints, they inherit the same 'story'
- framework as bugs. That means they don't have most of the limitations of
- LP blueprints: you can comment in them, you can have tasks affecting multiple
- projects, you can even have multiple tasks affecting the same project and
- order them !
-
-*Project groups*
- Projects can be grouped together arbitrarily, and all 'project' views can
- be reused by project groups. That makes it easy to triage or track all
- tasks for projects within a given OpenStack program.
-
*Subscription*
Users should be able to subscribe to tasks (and get them in a specific view)
as well as subscribe to projects (have their own customized project group).
diff --git a/storyboard/projects/admin.py b/storyboard/projects/admin.py
index ad9bb7fc..8f087586 100644
--- a/storyboard/projects/admin.py
+++ b/storyboard/projects/admin.py
@@ -18,8 +18,10 @@ from django.contrib import admin
from storyboard.projects.models import Branch
from storyboard.projects.models import Milestone
from storyboard.projects.models import Project
+from storyboard.projects.models import ProjectGroup
admin.site.register(Branch)
admin.site.register(Project)
+admin.site.register(ProjectGroup)
admin.site.register(Milestone)
diff --git a/storyboard/projects/models.py b/storyboard/projects/models.py
index db565600..32830642 100644
--- a/storyboard/projects/models.py
+++ b/storyboard/projects/models.py
@@ -24,6 +24,15 @@ class Project(models.Model):
return self.name
+class ProjectGroup(models.Model):
+ name = models.CharField(max_length=50, primary_key=True)
+ title = models.CharField(max_length=100)
+ members = models.ManyToManyField(Project)
+
+ def __unicode__(self):
+ return self.name
+
+
class Branch(models.Model):
BRANCH_STATUS = (
('M', 'master'),
diff --git a/storyboard/projects/templates/projects.dashboard.html b/storyboard/projects/templates/projects.dashboard.html
index 95df0520..e813ec68 100644
--- a/storyboard/projects/templates/projects.dashboard.html
+++ b/storyboard/projects/templates/projects.dashboard.html
@@ -2,8 +2,13 @@
{% block content %}
-
{{ project.title }} ({{ project.name }})
- Interesting graphs and information shall be placed here.
+
{{ ref.title }} ({{ ref.name }})
+
Groups
+
+ {% for group in ref.projectgroup_set.all %}
+ - {{group.title}}
+ {% endfor %}
+
{% endblock %}
diff --git a/storyboard/projects/templates/projects.group.html b/storyboard/projects/templates/projects.group.html
new file mode 100644
index 00000000..1654e0bd
--- /dev/null
+++ b/storyboard/projects/templates/projects.group.html
@@ -0,0 +1,14 @@
+{% extends "projects.project.html" %}
+{% block content %}
+
+
+
Project group: {{ ref.title }} ({{ ref.name }})
+
Projects
+
+
+
+{% endblock %}
diff --git a/storyboard/projects/templates/projects.list_tasks.html b/storyboard/projects/templates/projects.list_tasks.html
index 41c05df1..2b6d4a01 100644
--- a/storyboard/projects/templates/projects.list_tasks.html
+++ b/storyboard/projects/templates/projects.list_tasks.html
@@ -3,7 +3,7 @@
{% block content %}
-
{{ title }} for {{ project.title }}
+
{{ title }} for {{ ref.name }}
@@ -12,6 +12,7 @@
Story |
Task |
{% if is_bug %}Branch | {% endif %}
+{% if is_group %}Project | {% endif %}
Assignee |
Milestone |
@@ -25,6 +26,7 @@
{{ task.story.title }} |
{{ task.title }} |
{% if is_bug %}{{ task.milestone.branch.name }} | {% endif %}
+{% if is_group %}{{ task.project.name }} | {% endif %}
{{ task.assignee.username }} |
{% if not task.milestone.undefined %}{{ task.milestone.name }}{% endif %} |
diff --git a/storyboard/projects/templates/projects.project.html b/storyboard/projects/templates/projects.project.html
index 230ac777..586f0962 100644
--- a/storyboard/projects/templates/projects.project.html
+++ b/storyboard/projects/templates/projects.project.html
@@ -2,22 +2,28 @@
{% block extranav %}
{% endblock %}
{% block modals %}
-{% include "stories.modal_addstory.html" with project=project.name story_type='bug' %}
-{% include "stories.modal_addstory.html" with project=project.name story_type='feature' %}
+{% if not is_group %}
+ {% include "stories.modal_addstory.html" with project=ref.name story_type='bug' %}
+ {% include "stories.modal_addstory.html" with project=ref.name story_type='feature' %}
+{% endif %}
{% endblock %}
diff --git a/storyboard/projects/utils.py b/storyboard/projects/utils.py
new file mode 100644
index 00000000..fe08bbf7
--- /dev/null
+++ b/storyboard/projects/utils.py
@@ -0,0 +1,26 @@
+# Copyright 2013 Thierry Carrez
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from storyboard.projects.models import Project
+from storyboard.projects.models import ProjectGroup
+
+
+def retrieve_projects(name, group):
+ if group:
+ ref = ProjectGroup.objects.get(name=name)
+ return ref, ref.members.all()
+ else:
+ ref = Project.objects.get(name=name)
+ return ref, [ref]
diff --git a/storyboard/projects/views.py b/storyboard/projects/views.py
index 8c35af9a..3f0859c1 100644
--- a/storyboard/projects/views.py
+++ b/storyboard/projects/views.py
@@ -16,6 +16,7 @@
from django.shortcuts import render
from storyboard.projects.models import Project
+from storyboard.projects.utils import retrieve_projects
from storyboard.stories.models import Task
@@ -25,60 +26,71 @@ def default_list(request):
})
-def dashboard(request, projectname):
- project = Project.objects.get(name=projectname)
- bugcount = Task.objects.filter(project=project,
+def dashboard(request, projectname, group=False):
+ ref, projects = retrieve_projects(projectname, group)
+ bugcount = Task.objects.filter(project__in=projects,
story__is_bug=True,
story__priority=0).count()
+ if group:
+ return render(request, "projects.group.html", {
+ 'ref': ref,
+ 'is_group': group,
+ 'bugtriagecount': bugcount,
+ })
return render(request, "projects.dashboard.html", {
- 'project': project,
+ 'ref': ref,
+ 'is_group': group,
'bugtriagecount': bugcount,
})
-def list_featuretasks(request, projectname):
- project = Project.objects.get(name=projectname)
- bugcount = Task.objects.filter(project=project,
+def list_featuretasks(request, projectname, group=False):
+ ref, projects = retrieve_projects(projectname, group)
+ bugcount = Task.objects.filter(project__in=projects,
story__is_bug=True,
story__priority=0).count()
- featuretasks = Task.objects.filter(project=project,
+ featuretasks = Task.objects.filter(project__in=projects,
story__is_bug=False,
status__in=['T', 'R'])
return render(request, "projects.list_tasks.html", {
'title': "Active feature tasks",
- 'project': project,
+ 'ref': ref,
+ 'is_group': group,
+ 'name': projectname,
'bugtriagecount': bugcount,
'tasks': featuretasks,
'is_bug': False,
})
-def list_bugtasks(request, projectname):
- project = Project.objects.get(name=projectname)
- bugcount = Task.objects.filter(project=project,
+def list_bugtasks(request, projectname, group=False):
+ ref, projects = retrieve_projects(projectname, group)
+ bugcount = Task.objects.filter(project__in=projects,
story__is_bug=True,
story__priority=0).count()
- bugtasks = Task.objects.filter(project=project,
+ bugtasks = Task.objects.filter(project__in=projects,
story__is_bug=True,
status__in=['T', 'R'])
return render(request, "projects.list_tasks.html", {
'title': "Active bug tasks",
- 'project': project,
+ 'ref': ref,
+ 'is_group': group,
'bugtriagecount': bugcount,
'tasks': bugtasks,
'is_bug': True,
})
-def list_bugtriage(request, projectname):
- project = Project.objects.get(name=projectname)
- tasks = Task.objects.filter(project=project,
+def list_bugtriage(request, projectname, group=False):
+ ref, projects = retrieve_projects(projectname, group)
+ tasks = Task.objects.filter(project__in=projects,
story__is_bug=True,
story__priority=0)
bugcount = tasks.count()
return render(request, "projects.list_tasks.html", {
'title': "Bugs needing triage",
- 'project': project,
+ 'ref': ref,
+ 'is_group': group,
'bugtriagecount': bugcount,
'tasks': tasks,
'is_bug': True,
diff --git a/storyboard/urls.py b/storyboard/urls.py
index 008ec61c..c37e4815 100644
--- a/storyboard/urls.py
+++ b/storyboard/urls.py
@@ -26,6 +26,7 @@ urlpatterns = patterns('',
(r'^$', 'storyboard.about.views.welcome'),
(r'^about/', include('storyboard.about.urls')),
(r'^project/', include('storyboard.projects.urls')),
+ (r'^projectgroup/', include('storyboard.projects.urls'), {'group': True}),
(r'^story/', include('storyboard.stories.urls')),
url(r'^admin/', include(admin.site.urls)),
(r'^logout$', 'storyboard.about.views.dologout'),