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

+
{% 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 @@ {% if is_bug %}{% endif %} +{% if is_group %}{% endif %} @@ -25,6 +26,7 @@ {% if is_bug %}{% endif %} +{% if is_group %}{% 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'),
Story TaskBranchProjectAssignee Milestone
{{ task.story.title }} {{ task.title }}{{ task.milestone.branch.name }}{{ task.project.name }}{{ task.assignee.username }} {% if not task.milestone.undefined %}{{ task.milestone.name }}{% endif %}