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:
parent
46fb071544
commit
8e40acbd70
10
manage.py
10
manage.py
@ -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)
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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.
|
1109
storyboard/about/static/css/bootstrap-responsive.css
vendored
1109
storyboard/about/static/css/bootstrap-responsive.css
vendored
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
6167
storyboard/about/static/css/bootstrap.css
vendored
6167
storyboard/about/static/css/bootstrap.css
vendored
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 |
2280
storyboard/about/static/js/bootstrap.js
vendored
2280
storyboard/about/static/js/bootstrap.js
vendored
File diff suppressed because it is too large
Load Diff
6
storyboard/about/static/js/bootstrap.min.js
vendored
6
storyboard/about/static/js/bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
@ -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;
|
||||
}
|
@ -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);
|
||||
};
|
||||
|
||||
}));
|
6
storyboard/about/static/js/jquery.min.js
vendored
6
storyboard/about/static/js/jquery.min.js
vendored
File diff suppressed because one or more lines are too long
@ -1,23 +0,0 @@
|
||||
var ARROW_TEMPLATE = " <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(); })
|
||||
}
|
@ -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;
|
||||
}
|
@ -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 %}
|
@ -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 »</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 »</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 »</a></p>
|
||||
</div><!--/span-->
|
||||
</div><!--/row-->
|
||||
{% endblock %}
|
@ -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>
|
@ -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'),
|
||||
)
|
@ -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('/')
|
@ -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'
|
@ -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.
|
@ -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)
|
@ -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']
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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 %}
|
@ -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">«</a></li>
|
||||
<li><a class="last-page-link">»</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 %}
|
@ -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 %}
|
@ -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'),
|
||||
)
|
@ -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
|
@ -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)
|
@ -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
|
@ -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.
|
@ -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)
|
@ -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)
|
@ -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 %}
|
@ -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 %}
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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 %}
|
@ -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.
|
@ -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')
|
@ -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)
|
@ -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'),
|
||||
)
|
@ -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)
|
@ -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)
|
@ -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'),
|
||||
)
|
@ -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)
|
Loading…
x
Reference in New Issue
Block a user