Get rid of Django

Remove everything related to Django since project is moving
towards SQLalchemy and Pecan/WSME.

Change-Id: I3a952fa4206875dff73911b45be0c933b62e8972
This commit is contained in:
Ruslan Kamaldinov 2014-01-10 20:50:58 +04:00
parent 46fb071544
commit 8e40acbd70
56 changed files with 1 additions and 11856 deletions

View File

@ -1,10 +0,0 @@
#!/usr/bin/env python
import os
import sys
if __name__ == "__main__":
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "storyboard.settings")
from django.core.management import execute_from_command_line
execute_from_command_line(sys.argv)

View File

@ -2,13 +2,9 @@ pbr>=0.5.21,<1.0
alembic>=0.4.1
Babel>=0.9.6
Django>=1.4,<1.6
django-openid-auth
iso8601>=0.1.8
markdown
oslo.config>=1.2.0
pecan>=0.2.0
python-openid
six>=1.4.1
SQLAlchemy>=0.8,<=0.8.99
WSME>=0.5b6

View File

@ -8,7 +8,7 @@ author-email = openstack-dev@lists.openstack.org
home-page = http://www.openstack.org/
classifier =
Environment :: OpenStack
Framework :: Django
Framework :: Pecan/WSME
Intended Audience :: Developers
Intended Audience :: Information Technology
Intended Audience :: System Administrators

View File

@ -1,14 +0,0 @@
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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.

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,15 +0,0 @@
var page_type;
function prepareAJAX() {
var csrftoken = $.cookie('csrftoken');
$.ajaxSetup({
crossDomain: false,
beforeSend: function(xhr, settings) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
});
}
function setPageType(type) {
page_type = type;
}

View File

@ -1,113 +0,0 @@
/*!
* jQuery Cookie Plugin v1.4.0
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2013 Klaus Hartl
* Released under the MIT license
*/
(function (factory) {
if (typeof define === 'function' && define.amd) {
// AMD. Register as anonymous module.
define(['jquery'], factory);
} else {
// Browser globals.
factory(jQuery);
}
}(function ($) {
var pluses = /\+/g;
function encode(s) {
return config.raw ? s : encodeURIComponent(s);
}
function decode(s) {
return config.raw ? s : decodeURIComponent(s);
}
function stringifyCookieValue(value) {
return encode(config.json ? JSON.stringify(value) : String(value));
}
function parseCookieValue(s) {
if (s.indexOf('"') === 0) {
// This is a quoted cookie as according to RFC2068, unescape...
s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
}
try {
// Replace server-side written pluses with spaces.
// If we can't decode the cookie, ignore it, it's unusable.
// If we can't parse the cookie, ignore it, it's unusable.
s = decodeURIComponent(s.replace(pluses, ' '));
return config.json ? JSON.parse(s) : s;
} catch(e) {}
}
function read(s, converter) {
var value = config.raw ? s : parseCookieValue(s);
return $.isFunction(converter) ? converter(value) : value;
}
var config = $.cookie = function (key, value, options) {
// Write
if (value !== undefined && !$.isFunction(value)) {
options = $.extend({}, config.defaults, options);
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
return (document.cookie = [
encode(key), '=', stringifyCookieValue(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(''));
}
// Read
var result = key ? undefined : {};
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling $.cookie().
var cookies = document.cookie ? document.cookie.split('; ') : [];
for (var i = 0, l = cookies.length; i < l; i++) {
var parts = cookies[i].split('=');
var name = decode(parts.shift());
var cookie = parts.join('=');
if (key && key === name) {
// If second argument (value) is a function it's a converter...
result = read(cookie, value);
break;
}
// Prevent storing a cookie that we couldn't decode.
if (!key && (cookie = read(cookie)) !== undefined) {
result[name] = cookie;
}
}
return result;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) === undefined) {
return false;
}
// Must not alter options, thus extending a fresh object...
$.cookie(key, '', $.extend({}, options, { expires: -1 }));
return !$.cookie(key);
};
}));

File diff suppressed because one or more lines are too long

View File

@ -1,23 +0,0 @@
var ARROW_TEMPLATE = "&nbsp<i class='icon-arrow-$type'></i>";
function bindOrderingHandlers() {
$(".sortable-column a").click(function() {
var $this = $(this);
var order_key = $this.parent("th").attr("data-order-key");
setTaskOrdering(order_key);
});
}
function addOrderArrow(field, type) {
$(".sortable-column[data-order-key=" + field +"] a").append(
ARROW_TEMPLATE.replace("$type", type)
);
}
function setTaskOrdering(field) {
prepareAJAX();
$.post("/project/setorder/", {"page_type": page_type,
"order_field": field})
.success(function() { window.location.reload(); })
}

View File

@ -1,54 +0,0 @@
//Set pagination dropdown to value in request
function setPaginationDropdownValue(value) {
var readable_value = value + " tasks per page";
if (value == "-1") {
readable_value = "All tasks";
}
$("#pagination_current_value").prepend(readable_value);
}
//Reload current page with new pagination
function updatePagination(page_size) {
//show the first page
var page_number = 0;
var url = clearUrl(window.location.href);
url += "?page_size=" + page_size + "&page_number=" + page_number;
window.location.href = url;
}
function setPaginationLinks(page_count, page_number, page_size) {
var url = clearUrl(window.location.href);
$(".fist-page-link").attr("href", url + "?page_size=" + page_size + "&page_number=0");
//generate buttons 3 to the left and 3 to the right of current page_number
var link_template = "<li><a href='$url' id='$id'>$text</a></li>";
for (var i = Math.max(0, page_number - 3); i <= Math.min(Math.max(0, page_count - 1), page_number + 3); i++) {
$(".last-page-link").parent("li").before(link_template
.replace("$url", url + "?page_size=" + page_size + "&page_number=" + i)
.replace("$text", (i + 1).toString())
.replace("$id", "page-link-" + i));
}
var current_button = $("#page-link-" + page_number).parent("li");
current_button.addClass("disabled");
current_button.addClass("active");
$(".last-page-link").attr("href", url + "?page_size=" + page_size + "&page_number=" + Math.max(0, page_count - 1));
}
function bindPaginationDropdownHandlers() {
$("#page-size li a").click(function () {
$this = $(this);
selected_size = $this.attr("data-value");
updatePagination(selected_size);
})
}
function clearUrl(url) {
if (url.indexOf("?") != -1) {
url = url.split("?")[0]
}
return url;
}

View File

@ -1,12 +0,0 @@
{% extends "base.html" %}
{% block main %}
<div class="row-fluid">
<div class="span10 offset1">
{% block content %}
{% endblock %}
</div>
</div>
{% endblock %}
{% block script %}
<script type="text/javascript">$("#tab-about").addClass('active')</script>
{% endblock %}

View File

@ -1,25 +0,0 @@
{% extends "about.base.html" %}
{% block content %}
<div class="hero-unit">
<h1>StoryBoard</h1>
<h3>A task tracking system for inter-related projects.</h3>
<p>StoryBoard lets you track what needs to be done across projects and branches. It is a proof-of-concept demo of what the ideal OpenStack task tracker would look like. It may or may not end up replacing Launchpad Bugs/Blueprints for OpenStack task tracking and release management.</p>
</div>
<div class="row-fluid">
<div class="span4">
<h2>Stories</h2>
<p>It all begins with a <strong>story</strong>. A story is a bug report or proposed feature. Stories are then further split into <strong>tasks</strong>, which affect a given project and branch. You can easily track backports of bugs to a specific branch, or plan cross-project features.</p>
<p><a class="btn" href="/story">Access stories &raquo;</a></p>
</div><!--/span-->
<div class="span4">
<h2>Projects</h2>
<p>StoryBoard lets you efficiently track your work across a large number of interrelated projects. Flexible <strong>project groups</strong> lets you get the views that makes the most sense to you.</p>
<p><a class="btn" href="/project">Access projects &raquo;</a></p>
</div><!--/span-->
<div class="span4">
<h2>But why ?</h2>
<p>The OpenStack project is now running into a number of limitations and annoying differences in workflow with Launchpad. At the same time, Launchpad development stalled, leaving us with little chances to improve the tool to suit our needs. This POC reuses key Launchpad concepts (like bug tasks) and goes beyond.</p>
<p><a class="btn" href="https://github.com/ttx/storyboard/blob/master/README.rst">See project README &raquo;</a></p>
</div><!--/span-->
</div><!--/row-->
{% endblock %}

View File

@ -1,89 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>Storyboard</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="/static/css/bootstrap.min.css" rel="stylesheet" media="screen">
<style type="text/css">
body {
padding-top: 60px;
padding-bottom: 40px;
}
.sidebar-nav {
padding: 9px 0;
}
.after-buttongroup {
padding-top: 12px;
}
@media (max-width: 980px) {
/* Enable use of floated navbar text */
.navbar-text.pull-right {
float: none;
padding-left: 5px;
padding-right: 5px;
}
}
.btn-micro {
margin-top: -1px;
padding: 1px;
line-height: 14px;
}
.btn-micro [class^="icon-"],
.btn-micro [class*=" icon-"] {
margin-top: 0px;
}
</style>
</head>
<body>
<div class="navbar navbar-inverse navbar-fixed-top">
<div class="navbar-inner">
<div class="container-fluid">
<button type="button" class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<span class="brand">OpenStack</span>
<div class="nav-collapse collapse">
<ul class="nav">
<li id="tab-projects"><a href="/project">Projects</a></li>
<li id="tab-stories"><a href="/story">Stories</a></li>
<li id="tab-about"><a href="/">About</a></li>
</ul>
<ul class="nav pull-right">
{% if user.is_authenticated %}
<li class="dropdown">
<a href="#" class="dropdown-toggle" data-toggle="dropdown">{{ user.first_name }} {{ user.last_name }} ({{ user.username }})<b class="caret"></b></a>
<ul class="dropdown-menu">
<li class="disabled"><a href="#">Preferences</a></li>
<li class="divider"></li>
<li><a href="/logout">Log out</a></li>
</ul>
</li>
{% else %}
<li><a href="/openid/login?next={{ req.path }}">Log in</a></li>
{% endif %}
</ul>
</div><!--/.nav-collapse -->
</div>
</div>
</div>
<div class="container-fluid">
{% block main %}
{% endblock %}
<hr>
<footer>
<p>Powered by
<a href="https://github.com/ttx/storyboard">StoryBoard</a>.</p>
</footer>
</div><!--/.fluid-container-->
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/jquery.cookie.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
{% block postscript %}
{% endblock %}
</body>
</html>

View File

@ -1,21 +0,0 @@
# Copyright 2011 Thierry Carrez <thierry@openstack.org>
# 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 django.conf.urls.defaults import patterns
urlpatterns = patterns('storyboard.about.views',
(r'^$', 'welcome'),
)

View File

@ -1,27 +0,0 @@
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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 django.contrib.auth import logout
from django.http import HttpResponseRedirect
from django.shortcuts import render
def welcome(request):
return render(request, "about.welcome.html")
def dologout(request):
logout(request)
return HttpResponseRedirect('/')

View File

@ -1,53 +0,0 @@
# Django local settings for storyboard project.
# coding=UTF-8
#
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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.
#DEBUG = True
#TEMPLATE_DEBUG = DEBUG
#ADMINS = (
# # (u'Your Name', 'your_email@example.com'),
#)
#
#MANAGERS = ADMINS
#
#DATABASES = {
# 'default': {
# 'NAME': 'storyboard.db',
# 'ENGINE': 'django.db.backends.sqlite3',
# }
#}
# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/1.4/ref/settings/#allowed-hosts
#ALLOWED_HOSTS = []
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
#TIME_ZONE = 'America/Chicago'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
#LANGUAGE_CODE = 'en-us'
#SITE_ID = 1
# Make this unique, and don't share it with anybody.
#SECRET_KEY = u'my_secret_key_here'

View File

@ -1,14 +0,0 @@
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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.

View File

@ -1,27 +0,0 @@
# Copyright 2011 Thierry Carrez <thierry@openstack.org>
# 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 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)

View File

@ -1,64 +0,0 @@
# Copyright 2011 Thierry Carrez <thierry@openstack.org>
# 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 django.db import models
class Project(models.Model):
name = models.CharField(max_length=50, primary_key=True)
title = models.CharField(max_length=100)
def __unicode__(self):
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'),
('R', 'release'),
('S', 'stable'),
('U', 'unsupported'))
name = models.CharField(max_length=50)
short_name = models.CharField(max_length=20)
status = models.CharField(max_length=1, choices=BRANCH_STATUS)
release_date = models.DateTimeField()
def __unicode__(self):
return self.name
class Meta:
ordering = ['release_date']
class Milestone(models.Model):
name = models.CharField(max_length=50)
branch = models.ForeignKey(Branch)
released = models.BooleanField(default=False)
undefined = models.BooleanField(default=False)
def __unicode__(self):
return self.name
class Meta:
ordering = ['name']

View File

@ -1,28 +0,0 @@
{% extends "base.html" %}
{% block main %}
<div class="row-fluid">
<div class="span2">
<div class="well sidebar-nav">
<ul class="nav nav-list">
<li class="nav-header">Projects</li>
<li class="disabled"><a href="#">Search projects</a></li>
{% if user.is_authenticated %}
<li class="nav-header">My projects</li>
<li class="disabled"><a href="#">Subscribe to ...</a></li>
{% endif %}
</ul>
</div><!--/.well -->
{% block extranav %}
{% endblock %}
</div><!--/span-->
<div class="span10">
{% block content %}
{% endblock %}
</div><!--/span-->
</div>
{% endblock %}
{% block postscript %}
<script type="text/javascript">$("#tab-projects").addClass('active')</script>
{% block modals %}
{% endblock %}
{% endblock %}

View File

@ -1,14 +0,0 @@
{% extends "projects.project.html" %}
{% block content %}
<div class="row-fluid">
<div class="span12">
<h3>{{ ref.title }} ({{ ref.name }})</h3>
<h4>Groups</h4>
<ul>
{% for group in ref.projectgroup_set.all %}
<li><a href="/projectgroup/{{group.name}}">{{group.title}}</a></li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View File

@ -1,14 +0,0 @@
{% extends "projects.project.html" %}
{% block content %}
<div class="row-fluid">
<div class="span12">
<h3>Project group: {{ ref.title }} ({{ ref.name }})</h3>
<h4>Projects</h4>
<ul>
{% for project in ref.members.all %}
<li><a href="/project/{{ project.name }}">{{ project.title }}</a></li>
{% endfor %}
</ul>
</div>
</div>
{% endblock %}

View File

@ -1,24 +0,0 @@
{% extends "projects.base.html" %}
{% block content %}
<div class="row-fluid">
<div class="span12">
<h3>Projects</h3>
<table class="table table-striped table-hover">
<thead>
<tr>
<th>Name</th>
<th>Title</th>
</tr>
</thead>
<tbody>
{% for project in projects %}
<tr>
<td><a href="/project/{{ project.name }}">{{ project.name }}</a></td>
<td>{{ project.title }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@ -1,79 +0,0 @@
{% extends "projects.project.html" %}
{% load storyviewfilters %}
{% block content %}
<div class="row-fluid">
<div class="span12">
<h3 class="span6">{{ title }} for {{ ref.name }}</h3>
<table class="table table-condensed table-hover">
<thead>
<tr>
<th class="sortable-column" data-order-key="story__priority"><a href="#">Priority</a></th>
<th class="sortable-column" data-order-key="id"><a href="#">#</a></th>
<th class="sortable-column" data-order-key="story__title"><a href="#">Story</a></th>
<th class="sortable-column" data-order-key="title"><a href="#">Task</a></th>
{% if is_bug %}<th class="sortable-column" data-order-key="milestone__branch__name"><a href="#">Branch</a></th>{% endif %}
{% if is_group %}<th class="sortable-column" data-order-key="project__name"><a href="#">Project</a></th>{% endif %}
<th class="sortable-column" data-order-key="assignee__username"><a href="#">Assignee</a></th>
<th class="sortable-column" data-order-key="milestone__name"><a href="#">Milestone</a>
</th>
</tr>
</thead>
<tbody>
{% for task in tasks %}
<tr class="{{ task.status|taskcolor }}">
<td><span class="badge{{ task.story.priority|priobadge }}">
{{ task.story.get_priority_display }}</span></td>
<td>{{ task.id }}</td>
<td><small><a href="/story/{{task.story.id}}">{{ task.story.title }}</a></small></td>
<td>{{ task.title }}</td>
{% if is_bug %}<td>{{ task.milestone.branch.name }}</td>{% endif %}
{% if is_group %}<td>{{ task.project.name }}</td>{% endif %}
<td>{{ task.assignee.username }}</td>
<td>{% if not task.milestone.undefined %}{{ task.milestone.name }}{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
<div class="row-fluid">
<div class="pagination span3">
<ul>
<li><a class="fist-page-link">&laquo</a></li>
<li><a class="last-page-link">&raquo</a></li>
</ul>
</div>
<div class="pull-right" style="margin-top: 20px">
<div class="btn-group">
<a class="btn dropdown-toggle" href="#" data-toggle="dropdown" id="pagination_current_value">
<span class="caret"></span></a>
<ul class="dropdown-menu" id="page-size">
<li><a data-value="15">15 tasks per page</a></li>
<li><a data-value="30">30 tasks per page</a></li>
<li><a data-value="50">50 tasks per page</a></li>
<li><a data-value="100">100 tasks per page</a></li>
<li class="divider"></li>
<li><a data-value="-1">All tasks</a></li>
</ul>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block postscript %}
<script src="/static/js/common.js"></script>
<script src="/static/js/pagination.js"></script>
<script src="/static/js/ordering.js"></script>
<script type="text/javascript">$(function() {setPaginationDropdownValue("{{ page_size }}")})</script>
<script type="text/javascript">$(function() {bindPaginationDropdownHandlers()})</script>
<script type="text/javascript">$(function() {setPaginationLinks({{ page_count }}, {{ page_number }}, {{ page_size }})})</script>
<script type="text/javascript">$(function() {setPageType("{{ page_type }}")})</script>
<script type="text/javascript">$(function() {bindOrderingHandlers()})</script>
<script type="text/javascript">
$(function() {
{% for field, type in arrow_object.items %}
addOrderArrow("{{ field }}", "{{ type }}");
{% endfor %}
})
</script>
{% endblock %}

View File

@ -1,29 +0,0 @@
{% extends "projects.base.html" %}
{% block extranav %}
<div class="well sidebar-nav">
<ul class="nav nav-list">
<li class="nav-header">{{ref.name}}</li>
<li><a href="/project{% if is_group %}group{% endif %}/{{ref.name}}">Dashboard</a></li>
<li class="divider"></li>
<li><a href="/project{% if is_group %}group{% endif %}/{{ref.name}}/bugs">List bug tasks</a></li>
<li><a href="/project{% if is_group %}group{% endif %}/{{ref.name}}/bugs/triage">Triage bugs
{% if bugtriagecount > 0 %}<span class="badge
{% if bugtriagecount < 20 %}badge-success{% else %}{% if bugtriagecount < 50 %}badge-warning{% else %}badge-important{% endif %}{% endif %}">
{{ bugtriagecount }}</span>{% endif %}</a></li>
{% if not is_group %}
<li><a href="#addbug" data-toggle="modal">Report new bug</a></li>
{% endif %}
<li class="divider"></li>
<li><a href="/project{% if is_group %}group{% endif %}/{{ref.name}}/features">List feature tasks</a></li>
{% if not is_group %}
<li><a href="#addfeature" data-toggle="modal">Propose new feature</a></li>
{% endif %}
</ul>
</div><!--/.well -->
{% endblock %}
{% block modals %}
{% 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 %}

View File

@ -1,26 +0,0 @@
# Copyright 2011 Thierry Carrez <thierry@openstack.org>
# 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 django.conf.urls.defaults import patterns
urlpatterns = patterns('storyboard.projects.views',
(r'^$', 'default_list'),
(r'^setorder/$', 'set_order'),
(r'^(.+)/bugs/triage$', 'list_bugtriage'),
(r'^(.+)/bugs$', 'list_bugtasks'),
(r'^(.+)/features$', 'list_featuretasks'),
(r'^(.+)$', 'dashboard'),
)

View File

@ -1,89 +0,0 @@
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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 collections import OrderedDict
import six
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]
def order_results(request, page_type, tasks):
order_dict = request.session.get("order_dict")
if not order_dict:
order_dict = dict()
request.session["order_dict"] = order_dict
if page_type not in order_dict:
order_dict[page_type] = build_default_order_dict()
order_list = []
for key, val in six.iteritems(order_dict[page_type]):
order_param = key
if val == "desc":
order_param = "-%s" % order_param
order_list.append(order_param)
return tasks.order_by(*order_list)
def build_order_arrows_object(request, page_type):
order_dict = request.session.get("order_dict")
if not order_dict:
return {}
page_order_fields = order_dict.get(page_type)
if not page_order_fields:
return {}
arrows_object = {}
for field, order in six.iteritems(page_order_fields):
arrows_object[field] = "up" if order == "asc" else "down"
return arrows_object
def get_pagination(request, total_count):
page_size = int(request.GET.get("page_size", 15))
page_number = int(request.GET.get("page_number", 0))
if page_number < -1:
raise RuntimeError("Invalid page number")
page_count = total_count / page_size
if total_count % page_size > 0:
page_count += 1
return page_size, page_count, page_number
def build_default_order_dict():
_dict = OrderedDict()
_dict["story__priority"] = "desc"
return _dict

View File

@ -1,180 +0,0 @@
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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 collections import OrderedDict
from django.http import HttpResponse
from django.shortcuts import render
from django.views.decorators.http import require_POST
from storyboard.projects.models import Project
from storyboard.projects import utils
from storyboard.stories.models import Task
def default_list(request):
return render(request, "projects.list.html", {
'projects': Project.objects.all(),
})
def dashboard(request, projectname, group=False):
ref, projects = utils.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", {
'ref': ref,
'is_group': group,
'bugtriagecount': bugcount,
})
def list_featuretasks(request, projectname, group=False):
page_type = "featuretasks"
ref, projects = utils.retrieve_projects(projectname, group)
bugcount = Task.objects.filter(project__in=projects,
story__is_bug=True,
story__priority=0).count()
featuretasks = Task.objects.filter(project__in=projects,
story__is_bug=False,
status__in=['T', 'R'])
featuretasks = utils.order_results(request, page_type, featuretasks)
p_size, p_count, p_number = utils.get_pagination(request,
len(featuretasks))
if p_size != -1:
featuretasks = featuretasks[p_number * p_size: (p_number + 1) * p_size]
arrow_object = utils.build_order_arrows_object(request, page_type)
return render(request, "projects.list_tasks.html", {
'title': "Active feature tasks",
'page_type': page_type,
'ref': ref,
'is_group': group,
'name': projectname,
'bugtriagecount': bugcount,
'tasks': featuretasks,
'page_count': p_count,
'page_number': p_number,
'page_size': p_size,
'arrow_object': arrow_object,
'is_bug': False
})
def list_bugtasks(request, projectname, group=False):
page_type = "bugtasks"
ref, projects = utils.retrieve_projects(projectname, group)
bugcount = Task.objects.filter(project__in=projects,
story__is_bug=True,
story__priority=0).count()
bugtasks = Task.objects.filter(project__in=projects,
story__is_bug=True,
status__in=['T', 'R'])
bugtasks = utils.order_results(request, page_type, bugtasks)
p_size, p_count, p_number = utils.get_pagination(request, len(bugtasks))
if p_size != -1:
bugtasks = bugtasks[p_number * p_size: (p_number + 1) * p_size]
arrow_object = utils.build_order_arrows_object(request, page_type)
return render(request, "projects.list_tasks.html", {
'title': "Active bug tasks",
'page_type': page_type,
'ref': ref,
'is_group': group,
'bugtriagecount': bugcount,
'tasks': bugtasks,
'page_count': p_count,
'page_number': p_number,
'page_size': p_size,
'arrow_object': arrow_object,
'is_bug': True,
})
def list_bugtriage(request, projectname, group=False):
page_type = "bugtriage"
ref, projects = utils.retrieve_projects(projectname, group)
tasks = Task.objects.filter(project__in=projects,
story__is_bug=True,
story__priority=0)
tasks = utils.order_results(request, page_type, tasks)
bugcount = tasks.count()
p_size, p_count, p_number = utils.get_pagination(request, len(tasks))
if p_size != -1:
tasks = tasks[p_number * p_size: (p_number + 1) * p_size]
arrow_object = utils.build_order_arrows_object(request, page_type)
return render(request, "projects.list_tasks.html", {
'title': "Bugs needing triage",
'page_type': page_type,
'ref': ref,
'is_group': group,
'bugtriagecount': bugcount,
'tasks': tasks,
'page_count': p_count,
'page_number': p_number,
'page_size': p_size,
'arrow_object': arrow_object,
'is_bug': True,
})
@require_POST
def set_order(request):
order_dict = request.session.get("order_dict", dict())
page_type = request.POST.get("page_type")
order_field = request.POST.get("order_field")
# multi_filed ordering will be implemented later with search requests
multi_field = request.POST.get("is_multi_field")
if not order_field:
raise RuntimeError("order_field is not set")
if page_type not in order_dict:
order_dict[page_type] = utils.build_default_order_dict()
order_type = order_dict[page_type].get(order_field)
if not multi_field:
order_dict[page_type] = OrderedDict()
if not order_type:
order_type = "desc"
else:
order_type = "asc" if order_type == "desc" else "desc"
order_dict[page_type][order_field] = order_type
# Save dict to session if it was recently created
request.session["order_dict"] = order_dict
return HttpResponse(status=202)

View File

@ -1,199 +0,0 @@
# Django settings for storyboard project.
#
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@example.com'),
)
MANAGERS = ADMINS
DATABASES = {
'default': {
'NAME': 'storyboard.db',
'ENGINE': 'django.db.backends.sqlite3',
}
}
# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/1.4/ref/settings/#allowed-hosts
ALLOWED_HOSTS = []
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# In a Windows environment this must be set to your system time zone.
TIME_ZONE = 'UTC'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale.
USE_L10N = True
# If you set this to False, Django will not use timezone-aware datetimes.
USE_TZ = True
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = ''
# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = ''
# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'
# Additional locations of static files
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
#'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# Make this unique, and don't share it with anybody.
SECRET_KEY = 'my_secret_key_here'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
#'django.template.loaders.eggs.Loader',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
# Uncomment the next line for simple clickjacking protection:
# 'django.middleware.clickjacking.XFrameOptionsMiddleware',
)
ROOT_URLCONF = 'storyboard.urls'
# Python dotted path to the WSGI application used by Django's runserver.
WSGI_APPLICATION = 'storyboard.wsgi.application'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or
# "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
INSTALLED_APPS = [
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.markup',
'django.contrib.messages',
'django.contrib.staticfiles',
'django_openid_auth',
'django.contrib.admin',
'storyboard.about',
'storyboard.projects',
'storyboard.stories',
]
AUTHENTICATION_BACKENDS = (
'django_openid_auth.auth.OpenIDBackend',
'django.contrib.auth.backends.ModelBackend',
)
# Should users be created when new OpenIDs are used to log in?
OPENID_CREATE_USERS = True
OPENID_STRICT_USERNAMES = True
# Can we reuse existing users?
OPENID_REUSE_USERS = True
# When logging in again, should we overwrite user details based on
# data received via Simple Registration?
OPENID_UPDATE_DETAILS_FROM_SREG = True
# If set, always use this as the identity URL rather than asking the
# user. This only makes sense if it is a server URL.
OPENID_SSO_SERVER_URL = 'https://login.launchpad.net/'
# Tell django.contrib.auth to use the OpenID signin URLs.
LOGIN_URL = '/openid/login'
LOGIN_REDIRECT_URL = '/'
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error when DEBUG=False.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'filters': {
'require_debug_false': {
'()': 'django.utils.log.RequireDebugFalse'
}
},
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
# Override settings with local ones.
try:
from storyboard.local_settings import * # noqa
except ImportError:
pass

View File

@ -1,14 +0,0 @@
# Copyright 2011 Thierry Carrez <thierry@openstack.org>
# 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.

View File

@ -1,26 +0,0 @@
# Copyright 2011 Thierry Carrez <thierry@openstack.org>
# 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 django.contrib import admin
from storyboard.stories.models import Comment
from storyboard.stories.models import Story
from storyboard.stories.models import StoryTag
from storyboard.stories.models import Task
admin.site.register(Story)
admin.site.register(Task)
admin.site.register(Comment)
admin.site.register(StoryTag)

View File

@ -1,73 +0,0 @@
# Copyright 2011 Thierry Carrez <thierry@openstack.org>
# 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 django.contrib.auth.models import User
from django.db import models
from storyboard.projects.models import Milestone
from storyboard.projects.models import Project
class Story(models.Model):
STORY_PRIORITIES = (
(4, 'Critical'),
(3, 'High'),
(2, 'Medium'),
(1, 'Low'),
(0, 'Undefined'),
)
creator = models.ForeignKey(User)
title = models.CharField(max_length=100)
description = models.TextField()
is_bug = models.BooleanField(default=True)
priority = models.IntegerField(choices=STORY_PRIORITIES)
def __unicode__(self):
return str(self.id)
class Task(models.Model):
TASK_STATUSES = (
('T', 'Todo'),
('R', 'In review'),
('L', 'Landed'),
)
story = models.ForeignKey(Story)
title = models.CharField(max_length=100, blank=True)
project = models.ForeignKey(Project)
assignee = models.ForeignKey(User, blank=True, null=True)
status = models.CharField(max_length=1, choices=TASK_STATUSES, default='T')
milestone = models.ForeignKey(Milestone)
def __unicode__(self):
return "%s %s/%s" % (
self.story.id, self.project.name, self.milestone.branch.short_name)
class Comment(models.Model):
story = models.ForeignKey(Story)
posted_date = models.DateTimeField(auto_now=True)
author = models.ForeignKey(User)
action = models.CharField(max_length=150, blank=True)
comment_type = models.CharField(max_length=20)
content = models.TextField(blank=True)
class Meta:
ordering = ['posted_date']
class StoryTag(models.Model):
story = models.ForeignKey(Story)
name = models.CharField(max_length=20)

View File

@ -1,32 +0,0 @@
{% extends "base.html" %}
{% block main %}
<div class="row-fluid">
<div class="span2">
<div class="well sidebar-nav">
<ul class="nav nav-list">
<li class="nav-header">Stories</li>
<li><a href="#addbug" data-toggle="modal">Add new bug</a></li>
<li><a href="#addfeature" data-toggle="modal">Add new feature</a></li>
<li class="disabled"><a href="#">Search stories</a></li>
<li class="nav-header">Reports</li>
<li class="disabled"><a href="#">A report</a></li>
</ul>
</div><!--/.well -->
{% block extranav %}
{% endblock %}
</div><!--/span-->
<div class="span10">
{% block content %}
{% endblock %}
</div><!--/span-->
</div>
{% endblock %}
{% block postscript %}
<script type="text/javascript">
$("#tab-stories").addClass('active');
</script>
{% include "stories.modal_addstory.html" with story_type='bug' %}
{% include "stories.modal_addstory.html" with story_type='feature' %}
{% block modals %}
{% endblock %}
{% endblock %}

View File

@ -1,49 +0,0 @@
{% extends "stories.base.html" %}
{% load storyviewfilters %}
{% block content %}
<div class="row-fluid">
<div class="span12">
<h3>Stories</h3>
<h5>Recent bugs</h5>
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>#</th>
<th>Bug</th>
<th>Priority</th>
</tr>
</thead>
<tbody>
{% for story in recent_bugs %}
<tr>
<td>{{ story.id }}</td>
<td><small><a href="/story/{{story.id}}">{{ story.title }}</a></small></td>
<td><span class="badge{{ story.priority|priobadge }}">
{{ story.get_priority_display }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
<h5>Recent features</h5>
<table class="table table-condensed table-hover">
<thead>
<tr>
<th>#</th>
<th>Feature</th>
<th>Priority</th>
</tr>
</thead>
<tbody>
{% for story in recent_features %}
<tr>
<td>{{ story.id }}</td>
<td><small><a href="/story/{{story.id}}">{{ story.title }}</a></small></td>
<td><span class="badge{{ story.priority|priobadge }}">
{{ story.get_priority_display }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endblock %}

View File

@ -1,40 +0,0 @@
<form method="POST" action="/story/new">{% csrf_token %}
<div id="add{{ story_type }}" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="add{{ story_type }}Label" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="add{{ story_type }}Label">Add new {{ story_type }}</h3>
</div>
<div class="modal-body">
<label>Affected projects <small>(optional)</small></label>
<div class="input-prepend">
<span class="add-on"><i class="icon-cog"></i></span>
<input class="input-block-level" name="projects" id="prependedInput"
type="text" value="{{ project }}">
</span>
</div>
<label>Title</label>
<input class="input-block-level" name="title"
type="text" placeholder="Short description of the {{ story_type}}" value="">
<label>Description <small>(can use Markdown)</small></label>
<textarea class="input-block-level" name="description"
{% if story_type == 'bug' %}
placeholder="enter bug description here. please include the version of the software used and detailed steps to reproduce."
{% else %}
placeholder="enter feature description here."
{% endif %}
rows="7"></textarea>
<label>Tags <small>(optional)</small></label>
<div class="input-prepend">
<span class="add-on"><i class="icon-tags"></i></span>
<input class="input-block-level" name="tags" id="prependedInput"
type="text" value="">
</span>
</div>
</div>
<div class="modal-footer">
<input type="hidden" name="story_type" value="{% if story_type == 'bug' %}1{% endif %}">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<input class="btn btn-primary" type="submit" value="Create {{story_type}}">
</div>
</div>
</form>

View File

@ -1,68 +0,0 @@
<form method="POST" action="/story/{{story.id}}/addtask">{% csrf_token %}
<div id="addtask" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="addtaskLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="addtaskLabel">Add new task</h3>
</div>
<div class="modal-body">
<label>Title <small>(optional)</small></label>
<input class="input-block-level" name="title"
type="text" placeholder="Optional task description" value="">
<label>Project</label>
<div class="input-prepend">
<span class="add-on"><i class="icon-cog"></i></span>
<input class="input-block-level" name="project" id="prependedInput"
type="text" value="">
</div>
{% if story.is_bug %}
<label>Branch / Milestone</label>
{% regroup milestones by branch as branch_list %}
<div class="btn-toolbar" data-toggle="buttons-radio">
{% for branch in branch_list %}
<div class="btn-group">
<button type="button" data-value="{{ milestone.id }}"
class="btn btn-small disabled"><b>{{branch.grouper.name}}</b></button>
{% for milestone in branch.list %}
{% if milestone.branch.status == 'M' and milestone.undefined %}
<button type="button" data-value="{{ milestone.id }}"
class="addtask_milestone btn btn-small active">{{milestone.name}}</button>
{% else %}
<button type="button" data-value="{{ milestone.id }}"
class="addtask_milestone btn btn-small">{{ milestone.name }}</button>
{% endif %}
{% endfor %}
</div>
{% endfor %}
</div>
{% else %}
<label>Milestone</label>
<div class="btn-group" data-toggle="buttons-radio">
{% for milestone in milestones %}
{% if milestone.branch.status == 'M' %}
{% if milestone.undefined %}
<button type="button" data-value="{{ milestone.id }}"
class="addtask_milestone btn btn-small active">{{milestone.name}}</button>
{% else %}
<button type="button" data-value="{{ milestone.id }}"
class="addtask_milestone btn btn-small">{{ milestone.name }}</button>
{% endif %}
{% endif %}
{% endfor %}
</div>
{% endif %}
<label class="after-buttongroup">Comment</label>
<textarea class="input-block-level" rows="6" name="comment"
placeholder="Add a comment"></textarea>
<input type="hidden" id="addtask_milestone" name="milestone" value="">
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<input class="btn btn-primary" type="submit" value="Add task">
</div>
</div>
</form>
<script type="text/javascript">
$(".addtask_milestone").click(function() {
$("#addtask_milestone").val($(this).data("value"));
});
</script>

View File

@ -1,19 +0,0 @@
<form method="POST" action="/story/task/{{ task.id }}/delete">
<div id="deltask{{ task.id }}" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="deltaskLabel" aria-hidden="true">
{% csrf_token %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="deltaskLabel">Delete
{{task.project.name}}/{{task.branch.shortname}} task ?</h3>
</div>
<div class="modal-body">
<label>Comment</label>
<textarea class="input-block-level" name="comment" rows="3"
placeholder="Add a comment"></textarea>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<input class="btn btn-primary" type="submit" value="Delete task">
</div>
</div>
</form>

View File

@ -1,32 +0,0 @@
{% load storyviewfilters %}
<form method="POST" action="/story/{{ story.id }}/priority">
<div id="editprio" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="editpriolLabel" aria-hidden="true">
{% csrf_token %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="editprioLabel">Change priority</h3>
</div>
<div class="modal-body">
<label>Priority</label>
<div class="btn-group" data-toggle="buttons-radio">
{% for code, prio in priorities %}
<button type="button" data-value="{{code}}"
class="editprio_prio btn btn-small{{code|priobutton}}{% if story.priority == code %} active{% endif %}">{{ prio }}</button>
{% endfor %}
</div>
<label class="after-buttongroup">Comment</label>
<textarea class="input-block-level" rows="6" name="comment"
placeholder="Add a comment"></textarea>
<input type="hidden" id="editprio_prio" name="priority" value="{{ story.priority }}">
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<input class="btn btn-primary" type="submit" value="Save changes">
</div>
</div>
</form>
<script type="text/javascript">
$(".editprio_prio").click(function() {
$("#editprio_prio").val($(this).data("value"));
});
</script>

View File

@ -1,26 +0,0 @@
<form method="POST" action="/story/{{story.id}}/edit">{% csrf_token %}
<div id="editstory" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="editstoryLabel" aria-hidden="true">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="editstoryLabel">Modify story</h3>
</div>
<div class="modal-body">
<label>Title</label>
<input class="input-block-level" name="title"
type="text" value="{{ story.title }}">
<label>Description <small>(can use Markdown)</small></label>
<textarea class="input-block-level" name="description" rows="9">{{ story.description }}</textarea>
<label>Tags</label>
<div class="input-prepend">
<span class="add-on"><i class="icon-tags"></i></span>
<input class="input-block-level" name="tags" id="prependedInput" type="text"
value="{% for tag in story.storytag_set.all %}{{ tag.name }} {% endfor %}">
</span>
</div>
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<input class="btn btn-primary" type="submit" value="Save changes">
</div>
</div>
</form>

View File

@ -1,67 +0,0 @@
{% load storyviewfilters %}
<form method="POST" action="/story/task/{{ task.id }}">
<div id="edittask{{ task.id }}" class="modal hide fade" tabindex="-1" role="dialog" aria-labelledby="edittaskLabel" aria-hidden="true">
{% csrf_token %}
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
<h3 id="edittaskLabel">Edit {{task.project.name}}
{% if story.is_bug %}({{task.milestone.branch.short_name}}){% endif %}
task</h3>
</div>
<div class="modal-body">
<label>Title <small>(optional)</small></label>
<input type="text" name="title" value="{{ task.title }}">
<label>Assignee</label>
<div class="input-prepend">
<span class="add-on"><i class="icon-user"></i></span>
<input id="prependedInput" type="text" name="assignee"
value="{{task.assignee.username}}">
</div>
<label>Milestone</label>
<div class="btn-group" data-toggle="buttons-radio">
{% if task.milestone not in milestones %}
<button type="button" data-value="{{task.milestone.id}}"
class="btn btn-small active edittask_ms{{task.id}} btn-success">
{{ task.milestone.name }}</button>
{% endif %}
{% for milestone in milestones %}
{% if milestone.branch == task.milestone.branch %}}
<button type="button" data-value="{{milestone.id}}"
class="btn btn-small
{% if task.milestone == milestone %}active{% endif %}
{% if not milestone.released or task.milestone == milestone %}edittask_ms{{task.id}}{%endif%}
{% if milestone.released %}btn-success
{% if task.milestone != milestone %}disabled{%endif%}
{% endif %}"
{% if task.milestone != milestone and milestone.released %}disabled="disabled"{%endif%}>
{{ milestone.name }}</button>
{% endif %}
{% endfor %}
</div>
<label class="after-buttongroup">Status</label>
<div class="btn-group" data-toggle="buttons-radio">
{% for code, status in taskstatuses %}
<button type="button" data-value="{{code}}"
class="edittask_status{{ task.id }} btn btn-small btn-{{code|taskcolor}}{% if task.status == code %} active{% endif %}">{{ status }}</button>
{% endfor %}
</div>
<label class="after-buttongroup">Comment</label>
<textarea class="input-block-level" name="comment" rows="3"
placeholder="Add a comment"></textarea>
<input type="hidden" id="edittask_ms{{task.id}}" name="milestone" value="{{ task.milestone.id }}">
<input type="hidden" id="edittask_status{{task.id}}" name="status" value="{{ task.status }}">
</div>
<div class="modal-footer">
<button class="btn" data-dismiss="modal" aria-hidden="true">Close</button>
<input class="btn btn-primary" type="submit" value="Save changes">
</div>
</div>
</form>
<script type="text/javascript">
$(".edittask_status{{task.id}}").click(function() {
$("#edittask_status{{task.id}}").val($(this).data("value"));
});
$(".edittask_ms{{task.id}}").click(function() {
$("#edittask_ms{{task.id}}").val($(this).data("value"));
});
</script>

View File

@ -1,107 +0,0 @@
{% extends "stories.base.html" %}
{% load markup %}
{% load storyviewfilters %}
{% block extranav %}
<div class="well sidebar-nav">
<ul class="nav nav-list">
<li class="nav-header">This story</li>
<li><a href="#editprio" data-toggle="modal">Change priority</a></li>
<li><a href="#editstory" data-toggle="modal">Modify story</a></li>
<li><a href="#addtask" data-toggle="modal">Add task</a></li>
<li class="disabled"><a href="#">Order tasks</a></li>
</ul>
</div><!--/.well -->
{% endblock %}
{% block content %}
<div class="row-fluid">
<div class="span2">
<h3>{% if story.is_bug %}Bug{% else %}Feature{% endif %}
{{ story.id }}</h3>
</div>
<div class="span10">
<a href=#editprio data-toggle="modal">
<span class="badge{{ story.priority|priobadge }}">
{{ story.get_priority_display }}</span></a><br>
<h4>{{ story.title }}</h4>
</div>
</div>
<hr>
<div class="row-fluid">
<div class="span12">
{% if story.task_set.count %}
<table class="table table-condensed">
<thead>
<tr>
<th>Task</th>
<th>Project</th>
{% if story.is_bug %}
<th>Branch</th>
{% endif %}
<th>Assignee</th>
<th>Status</th>
<th>Milestone</th>
</tr>
</thead>
<tbody>
{% for task in story.task_set.all %}
<tr class="{{ task.status|taskcolor }}">
<td>{{ task.title }}</td>
<td>{{ task.project.title }}</td>
{% if story.is_bug %}
<td>{{ task.milestone.branch.name }}</td>
{% endif %}
<td>{{ task.assignee.username }}</td>
<td>{{ task.get_status_display }}</td>
<td>{% if not task.milestone.undefined %}{{ task.milestone.name }}{% endif %}</td>
<td>
<a href="#edittask{{ task.id }}" class="btn btn-micro" data-toggle="modal"><i class="icon-edit"></i></a>
<a href="#deltask{{ task.id }}" class="btn btn-micro" data-toggle="modal"><i class="icon-remove"></i></a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
<div class="row-fluid">
<div class="span12">
<div class="well well-small">
{{ story.description|markdown:"safe" }}
</div>
<p><i class="icon-tags"></i>
{% for tag in story.storytag_set.all %}
<span class="label">{{ tag.name }}</span>
{% endfor %}
</p>
</div>
</div>
<div class="row-fluid">
<table class="table table-striped">
<tbody>
{% for comment in story.comment_set.all %}
<tr><td colspan=2>
<i class="icon-{{ comment.comment_type }}"></i>
by {{ comment.author.first_name }} {{ comment.author.last_name }} ({{ comment.author.username }}) on {{ comment.posted_date|date:"o-m-d H:i" }}:
{% if comment.action %}<br>{{ comment.action }}{% endif %}
</td></tr>
<tr><td>{{ comment.content|markdown:"safe" }}</td></tr>
{% endfor %}
</tbody>
</table>
<form method="POST" action="/story/{{ story.id }}/comment">
{% csrf_token %}
<textarea name="content" class="input-block-level" rows="3" placeholder="Add a comment"></textarea>
<button class="btn btn-mini" type="submit">Add comment</button>
</form>
</div>
{% endblock %}
{% block modals %}
{% include "stories.modal_editstory.html" %}
{% include "stories.modal_addtask.html" %}
{% include "stories.modal_editprio.html" %}
{% for task in story.task_set.all %}
{% include "stories.modal_edittask.html" with task=task %}
{% include "stories.modal_deltask.html" with task=task %}
{% endfor %}
{% endblock %}

View File

@ -1,14 +0,0 @@
# Copyright 2011 Thierry Carrez <thierry@openstack.org>
# 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.

View File

@ -1,44 +0,0 @@
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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 django import template
register = template.Library()
badges = ['', ' badge-info', ' badge-success', ' badge-warning',
' badge-important']
buttons = ['', ' btn-info', ' btn-success', ' btn-warning', ' btn-danger']
taskcolors = {'T': 'info', 'R': 'warning', 'L': 'success'}
@register.filter(name='priobadge')
def priobadge(value):
if value < 5:
return badges[value]
else:
return badges[4]
@register.filter(name='priobutton')
def priobutton(value):
if value < 5:
return buttons[value]
else:
return buttons[4]
@register.filter(name='taskcolor')
def taskcolor(value):
return taskcolors.get(value, 'info')

View File

@ -1,43 +0,0 @@
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
#
# 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.
"""
This file demonstrates writing tests using the unittest module. These will pass
when you run "manage.py test".
"""
from django.test import TestCase
class SimpleTest(TestCase):
def test_basic_addition(self):
"""Tests that 1 + 1 always equals 2.
"""
self.assertEqual(1 + 1, 2)
def test_more_addition(self):
"""Tests that 2 + 1 always equals 3.
"""
self.assertEqual(2 + 1, 3)
def test_even_more_addtion(self):
"""Tests that 2 + 2 always equals 4.
"""
self.assertEqual(2 + 2, 4)
def test_yet_more_addition(self):
"""Tests that 3 + 2 always equals 5.
"""
self.assertEqual(3 + 2, 5)

View File

@ -1,29 +0,0 @@
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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 django.conf.urls.defaults import patterns
urlpatterns = patterns('storyboard.stories.views',
(r'^$', 'dashboard'),
(r'^(\d+)$', 'view'),
(r'^(\d+)/addtask$', 'add_task'),
(r'^new$', 'add_story'),
(r'^(\d+)/edit$', 'edit_story'),
(r'^(\d+)/comment$', 'comment'),
(r'^(\d+)/priority$', 'set_priority'),
(r'^task/(\d+)$', 'edit_task'),
(r'^task/(\d+)/delete$', 'delete_task'),
)

View File

@ -1,22 +0,0 @@
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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.
def format_taskname(task):
if not task.story.is_bug:
if not task.title:
return task.project.name
return "%s (%s)" % (task.project.name, task.title)
return "%s (%s)" % (task.project.name, task.milestone.branch.short_name)

View File

@ -1,255 +0,0 @@
# Copyright 2013 Thierry Carrez <thierry@openstack.org>
# 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 django.contrib.auth.decorators import login_required
from django.contrib.auth.models import User
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.views.decorators.http import require_POST
from storyboard.projects.models import Milestone
from storyboard.projects.models import Project
from storyboard.stories.models import Comment
from storyboard.stories.models import Story
from storyboard.stories.models import StoryTag
from storyboard.stories.models import Task
from storyboard.stories.utils import format_taskname
def dashboard(request):
recent_bugs = Story.objects.filter(is_bug=True).order_by("-id")[:5]
recent_features = Story.objects.filter(is_bug=False).order_by("-id")[:5]
return render(request, "stories.dashboard.html", {
'recent_bugs': recent_bugs,
'recent_features': recent_features,
})
def view(request, storyid):
story = Story.objects.get(id=storyid)
milestones = Milestone.objects.filter(
released=False).order_by('branch__release_date')
return render(request, "stories.view.html", {
'story': story,
'milestones': milestones,
'priorities': Story.STORY_PRIORITIES,
'taskstatuses': Task.TASK_STATUSES,
})
@login_required
@require_POST
def comment(request, storyid):
story = Story.objects.get(id=storyid)
if request.POST.get('content', False):
newcomment = Comment(story=story,
author=request.user,
comment_type="comment",
content=request.POST['content'])
newcomment.save()
return HttpResponseRedirect('/story/%s' % storyid)
@login_required
@require_POST
def set_priority(request, storyid):
story = Story.objects.get(id=storyid)
if 'priority' in request.POST:
priority = request.POST['priority']
if int(priority) != story.priority:
pr = story.get_priority_display()
story.priority = priority
story.save()
# We need to refresh the story to get get_priority_display to work
story = Story.objects.get(id=storyid)
msg = "Set priority: %s -> %s" % (pr, story.get_priority_display())
newcomment = Comment(story=story,
action=msg,
author=request.user,
comment_type="random",
content=request.POST.get('comment', ''))
newcomment.save()
return HttpResponseRedirect('/story/%s' % storyid)
@login_required
@require_POST
def add_story(request):
try:
newstory = Story(
title=request.POST['title'],
description=request.POST['description'],
creator=request.user,
is_bug=bool(request.POST['story_type']),
priority=0,
)
newstory.save()
proposed_projects = request.POST['projects'].split()
if proposed_projects:
master_undefined_milestone = Milestone.objects.get(
branch__status='M', undefined=True)
tasks = []
for project in proposed_projects:
tasks.append(Task(
story=newstory,
project=Project.objects.get(name=project),
milestone=master_undefined_milestone,
))
Task.objects.bulk_create(tasks)
proposed_tags = set(request.POST['tags'].split())
if proposed_tags:
tags = []
for tag in proposed_tags:
tags.append(StoryTag(story=newstory, name=tag))
StoryTag.objects.bulk_create(tags)
msg = 'Story created (%s)' % newstory.title
newcomment = Comment(story=newstory,
action=msg,
author=request.user,
comment_type="star-empty",
content='')
newcomment.save()
except KeyError:
pass
return HttpResponseRedirect('/story/%s' % newstory.id)
@login_required
@require_POST
def add_task(request, storyid):
story = Story.objects.get(id=storyid)
try:
if request.POST['project']:
milestone = None
if request.POST['milestone']:
milestone = Milestone.objects.get(id=request.POST['milestone'])
if not milestone or milestone.branch.status != 'M':
milestone = Milestone.objects.get(branch__status='M',
undefined=True)
newtask = Task(
story=story,
title=request.POST['title'],
project=Project.objects.get(name=request.POST['project']),
milestone=milestone,
)
newtask.save()
msg = "Added %s task " % format_taskname(newtask)
newcomment = Comment(story=story,
action=msg,
author=request.user,
comment_type="plus-sign",
content=request.POST.get('comment', ''))
newcomment.save()
except KeyError:
pass
return HttpResponseRedirect('/story/%s' % story.id)
@login_required
@require_POST
def edit_task(request, taskid):
task = Task.objects.get(id=taskid)
try:
actions = []
if (task.title != request.POST['title']):
actions.append("title")
task.title = request.POST['title']
milestone = Milestone.objects.get(id=int(request.POST['milestone']))
milestonename = milestone.name
if (milestone != task.milestone):
actions.append("milestone -> %s" % milestonename)
task.milestone = milestone
status = request.POST['status']
if (task.status != status):
task.status = status
actions.append("status -> %s" % task.get_status_display())
if not request.POST['assignee']:
assignee = None
assigneename = "None"
else:
assignee = User.objects.get(username=request.POST['assignee'])
assigneename = assignee.username
if (assignee != task.assignee):
actions.append("assignee -> %s" % assigneename)
task.assignee = assignee
if actions:
msg = "Updated %s task " % format_taskname(task)
msg += ", ".join(actions)
task.save()
newcomment = Comment(story=task.story,
action=msg,
author=request.user,
comment_type="tasks",
content=request.POST.get('comment', ''))
newcomment.save()
except KeyError:
pass
return HttpResponseRedirect('/story/%s' % task.story.id)
@login_required
@require_POST
def delete_task(request, taskid):
task = Task.objects.get(id=taskid)
task.delete()
msg = "Deleted %s task" % format_taskname(task)
newcomment = Comment(story=task.story,
action=msg,
author=request.user,
comment_type="remove-sign",
content=request.POST.get('comment', ''))
newcomment.save()
return HttpResponseRedirect('/story/%s' % task.story.id)
@login_required
@require_POST
def edit_story(request, storyid):
story = Story.objects.get(id=storyid)
storytags = set(x.name for x in StoryTag.objects.filter(story=story))
onlytags = True
try:
actions = []
if (story.title != request.POST['title']):
onlytags = False
actions.append("title")
story.title = request.POST['title']
if (story.description != request.POST['description']):
onlytags = False
actions.append("description")
story.description = request.POST['description']
proposed_tags = set(request.POST['tags'].split())
if proposed_tags != storytags:
actions.append("tags")
StoryTag.objects.filter(story=story).delete()
tags = []
for tag in proposed_tags:
tags.append(StoryTag(story=story, name=tag))
StoryTag.objects.bulk_create(tags)
if actions:
msg = "Updated story " + ", ".join(actions)
story.save()
if onlytags:
comment_type = "tags"
else:
comment_type = "align-left"
newcomment = Comment(story=story,
action=msg,
author=request.user,
comment_type=comment_type)
newcomment.save()
except KeyError as e:
print(e)
return HttpResponseRedirect('/story/%s' % story.id)

View File

@ -1,33 +0,0 @@
# Copyright 2011 Thierry Carrez <thierry@openstack.org>
# 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 django.conf.urls.defaults import include
from django.conf.urls.defaults import patterns
from django.conf.urls.defaults import url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
(r'^openid/', include('django_openid_auth.urls')),
(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'),
)

View File

@ -1,29 +0,0 @@
# flake8: noqa
"""
WSGI config for storyboard project.
This module contains the WSGI application used by Django's development server
and any production WSGI deployments. It should expose a module-level variable
named ``application``. Django's ``runserver`` and ``runfcgi`` commands discover
this application via the ``WSGI_APPLICATION`` setting.
Usually you will have the standard Django WSGI application here, but it also
might make sense to replace the whole Django WSGI application with a custom one
that later delegates to the Django one. For example, you could introduce WSGI
middleware here, or combine a Django application with an application of another
framework.
"""
import os
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "storyboard.settings")
# This application object is used by any WSGI server configured to use this
# file. This includes Django's development server, if the WSGI_APPLICATION
# setting points here.
from django.core.wsgi import get_wsgi_application
application = get_wsgi_application()
# Apply WSGI middleware here.
# from helloworld.wsgi import HelloWorldApplication
# application = HelloWorldApplication(application)