aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMilosz Wasilewski <milosz.wasilewski@linaro.org>2014-01-27 09:51:08 +0000
committerMilosz Wasilewski <milosz.wasilewski@linaro.org>2014-01-27 09:51:08 +0000
commit541ab774dc97821529122904fe3192577e684ff9 (patch)
tree6400417362dcfae50cafe05fd48b258002153658
parent0cc0164f5734060689102016f626be5919209e58 (diff)
parent19dffd8aa718f5127ba38bb5c6217b30358d539b (diff)
pull master from repo
Change-Id: If264f868db46bfa16be7c75a345c96c116504da2
-rw-r--r--.gitignore4
-rw-r--r--README2
-rw-r--r--ansible/README10
-rw-r--r--ansible/group_vars/all7
-rw-r--r--ansible/hosts3
-rw-r--r--ansible/roles/common/handlers/main.yml8
-rw-r--r--ansible/roles/common/tasks/apache.yml18
-rw-r--r--ansible/roles/common/tasks/install_deps.yml15
-rw-r--r--ansible/roles/common/tasks/main.yml3
-rw-r--r--ansible/roles/status/tasks/apache_conf.yml11
-rw-r--r--ansible/roles/status/tasks/clone_code.yml6
-rw-r--r--ansible/roles/status/tasks/cronjobs.yml10
-rw-r--r--ansible/roles/status/tasks/install_roadmap.yml35
-rw-r--r--ansible/roles/status/tasks/local_settings.yml6
-rw-r--r--ansible/roles/status/tasks/main.yml6
-rw-r--r--ansible/roles/status/tasks/wsgi.yml6
-rw-r--r--ansible/roles/status/templates/apache_production.conf56
-rw-r--r--ansible/roles/status/templates/apache_staging.conf47
-rw-r--r--ansible/roles/status/templates/apache_website.conf8
-rw-r--r--ansible/roles/status/templates/roadmap.wsgi21
-rw-r--r--ansible/roles/status/templates/roadmap_update_cron.sh32
-rw-r--r--ansible/secrets.yml5
-rw-r--r--ansible/site.yml11
-rwxr-xr-xbin/roadmap_expect.sh8
-rwxr-xr-xbin/roadmap_update.sh27
-rw-r--r--bin/wsgi_roadmap.wsgi20
-rw-r--r--linaroroadmap/local_settings.py.dev29
-rw-r--r--linaroroadmap/settings.py84
-rw-r--r--requirements.txt2
-rw-r--r--roadmap/helpers.py246
-rw-r--r--roadmap/management/commands/burndown_snapshot.py32
-rw-r--r--roadmap/management/commands/roadmap_import.py115
-rw-r--r--roadmap/models.py5
-rw-r--r--roadmap/static/css/img/16/delete.pngbin0 -> 665 bytes
-rw-r--r--roadmap/static/css/img/16/moveleft.pngbin0 -> 553 bytes
-rw-r--r--roadmap/static/css/img/16/moveright.pngbin0 -> 557 bytes
-rw-r--r--roadmap/static/css/img/16/new.pngbin0 -> 593 bytes
-rw-r--r--roadmap/static/css/img/16/zoomin.pngbin0 -> 441 bytes
-rw-r--r--roadmap/static/css/img/16/zoomout.pngbin0 -> 361 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-bg_flat_0_aaaaaa_40x100.pngbin180 -> 180 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-bg_flat_55_fbec88_40x100.pngbin182 -> 182 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-bg_glass_75_d0e5f5_1x400.pngbin124 -> 124 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-bg_glass_85_dfeffc_1x400.pngbin123 -> 123 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-bg_glass_95_fef1ec_1x400.pngbin119 -> 119 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.pngbin3457 -> 3457 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.pngbin104 -> 104 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.pngbin88 -> 88 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-icons_217bc0_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-icons_2e83ff_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-icons_469bdd_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-icons_6da8d5_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-icons_cd0a0a_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-icons_d8e7f3_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/images/ui-icons_f9bd01_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/redmond/jquery-ui-1.7.1.custom.css0
-rw-r--r--[-rwxr-xr-x]roadmap/static/css/ui.slider.extras.css0
-rw-r--r--[-rwxr-xr-x]roadmap/static/js/selectToUISlider.jQuery.js0
-rw-r--r--roadmap/templates/roadmap/component.html1
-rw-r--r--roadmap/templates/roadmap/roadmap.html2
-rw-r--r--roadmap/templates/roadmap/roadmap_index.html21
-rw-r--r--roadmap/templates/roadmap/timeline.html2
-rw-r--r--roadmap/urls.py4
-rw-r--r--roadmap/views.py24
63 files changed, 699 insertions, 253 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..073ceb0
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+*.pyc
+local_settings.py
+*.db
+*.swp
diff --git a/README b/README
index ee890ef..adf7959 100644
--- a/README
+++ b/README
@@ -4,6 +4,8 @@ Step-by-step installation instructions for production environment
apt-get install python-pip
apt-get install git
apt-get install apache2 libapache2-mod-wsgi
+apt-get install python-tz
+apt-get install expect
2. install virtualenvwrapper and create virtualenv
pip install virtualenvwrapper
diff --git a/ansible/README b/ansible/README
new file mode 100644
index 0000000..ccb0130
--- /dev/null
+++ b/ansible/README
@@ -0,0 +1,10 @@
+To run the configuration, it is necessary to fill in the secrets.yml file
+with the correct user names and passwords for the service used.
+
+The secrets.yml file contains variables for:
+ - Crowd user name and password
+ - Jira user name and password
+
+ Those values are necessary for the local_settings.py file to run the
+ application.
+ \ No newline at end of file
diff --git a/ansible/group_vars/all b/ansible/group_vars/all
new file mode 100644
index 0000000..8a32cd0
--- /dev/null
+++ b/ansible/group_vars/all
@@ -0,0 +1,7 @@
+# Common variables.
+install_base: /srv
+roadmap_repo: http://git.linaro.org/git/infrastructure/roadmap.git
+apache_user: www-data
+crowd_url: https://login.linaro.org:8443/crowd/rest
+jira_server: https://cards.linaro.org
+jira_sfid: 10301
diff --git a/ansible/hosts b/ansible/hosts
new file mode 100644
index 0000000..dd5fb49
--- /dev/null
+++ b/ansible/hosts
@@ -0,0 +1,3 @@
+[all]
+staging.status.linaro.org ansible_ssh_user=ubuntu role=staging install_dir=staging.status.linaro.org
+status.linaro.org ansible_ssh_user=ubuntu role=production install_dir=status.linaro.org
diff --git a/ansible/roles/common/handlers/main.yml b/ansible/roles/common/handlers/main.yml
new file mode 100644
index 0000000..7ec48c2
--- /dev/null
+++ b/ansible/roles/common/handlers/main.yml
@@ -0,0 +1,8 @@
+- name: restart-apache
+ service: name=apache2 state=restarted
+
+- name: stop-apache
+ service: name=apache2 state=stopped
+
+- name: reload-apache
+ service: name=apache2 state=reloaded
diff --git a/ansible/roles/common/tasks/apache.yml b/ansible/roles/common/tasks/apache.yml
new file mode 100644
index 0000000..8f05d84
--- /dev/null
+++ b/ansible/roles/common/tasks/apache.yml
@@ -0,0 +1,18 @@
+# Enables necessary Apache modules and disables websites.
+- name: enable-modules
+ command: a2enmod {{ item }}
+ with_items:
+ - wsgi
+ - headers
+ - expires
+ notify: restart-apache
+
+- name: disable-sites
+ command: a2dissite {{ item }}
+ with_items:
+ - default
+ notify: restart-apache
+
+# Make sure the web server is running.
+- name: apache2-started
+ service: name=apache2 state=started enabled=yes
diff --git a/ansible/roles/common/tasks/install_deps.yml b/ansible/roles/common/tasks/install_deps.yml
new file mode 100644
index 0000000..775f4bf
--- /dev/null
+++ b/ansible/roles/common/tasks/install_deps.yml
@@ -0,0 +1,15 @@
+# Install all dependencies required by roadmap.
+- name: install-os-deps
+ apt: name={{ item }}
+ with_items:
+ - apache2
+ - libapache2-mod-wsgi
+ - git
+ - python-pip
+ - python-tz
+
+# PIP installation if necessary.
+- name: install-pip-deps
+ pip: name={{ item }}
+ with_items:
+ - virtualenvwrapper
diff --git a/ansible/roles/common/tasks/main.yml b/ansible/roles/common/tasks/main.yml
new file mode 100644
index 0000000..1aa5b62
--- /dev/null
+++ b/ansible/roles/common/tasks/main.yml
@@ -0,0 +1,3 @@
+# Common tasks for all instances (production and staging).
+- include: install_deps.yml
+- include: apache.yml
diff --git a/ansible/roles/status/tasks/apache_conf.yml b/ansible/roles/status/tasks/apache_conf.yml
new file mode 100644
index 0000000..1c9e797
--- /dev/null
+++ b/ansible/roles/status/tasks/apache_conf.yml
@@ -0,0 +1,11 @@
+- name: apache-website-conf
+ template: src=apache_website.conf dest=/etc/apache2/sites-available/{{ install_dir }} owner=root group=root mode=0644
+ tags:
+ - apache-conf
+ notify: reload-apache
+
+- name: apache-website-enable
+ command: a2ensite {{ install_dir }}
+ notify: reload-apache
+ tags:
+ - apache-enable
diff --git a/ansible/roles/status/tasks/clone_code.yml b/ansible/roles/status/tasks/clone_code.yml
new file mode 100644
index 0000000..c64cdc8
--- /dev/null
+++ b/ansible/roles/status/tasks/clone_code.yml
@@ -0,0 +1,6 @@
+# Clone roadmap/status code and fix files and dirs permission.
+- name: clone-roadmap
+ git: name={{ roadmap_repo }} dest={{ install_base }}/{{ install_dir }}
+
+- name: fix-repo-owner
+ file: path={{ install_base}}/{{ install_dir }} recurse=yes owner={{ apache_user }} group={{ apache_user }}
diff --git a/ansible/roles/status/tasks/cronjobs.yml b/ansible/roles/status/tasks/cronjobs.yml
new file mode 100644
index 0000000..e650270
--- /dev/null
+++ b/ansible/roles/status/tasks/cronjobs.yml
@@ -0,0 +1,10 @@
+# Install necessary cronjobs.
+- name: copy-update-script
+ template: src=roadmap_update_cron.sh dest={{ install_base }}/{{ install_dir }}/bin/roadmap_update_cron.sh owner={{ apache_user }} group={{ apache_user }} mode=0770
+ tags:
+ - cronjob
+
+- name: install-update-cronjob
+ cron: name='Update cards' cron_file={{ install_dir }} state=present user={{ apache_user }} job={{ install_base }}/{{ install_dir }}/bin/roadmap_update_cron.sh minute=0 hour=0
+ tags:
+ - cronjob
diff --git a/ansible/roles/status/tasks/install_roadmap.yml b/ansible/roles/status/tasks/install_roadmap.yml
new file mode 100644
index 0000000..d998683
--- /dev/null
+++ b/ansible/roles/status/tasks/install_roadmap.yml
@@ -0,0 +1,35 @@
+# Install roadmap via virtualenv.
+- name: roadmap-log-directory
+ file: path=/var/log/roadmap state=directory owner={{ apache_user }} group={{apache_user }}
+
+- name: create-virtualenv
+ command: virtualenv --system-site-packages {{ install_base }}/virtualenv/{{ install_dir }}
+
+- name: install-requirements
+ pip: virtualenv={{ install_base }}/virtualenv/{{ install_dir }} requirements={{ install_base }}/{{ install_dir }}/requirements.txt
+
+# Roadmap installation steps.
+- name: roadmap-django-syncdb
+ django_manage: command=syncdb virtualenv={{ install_base }}/virtualenv/{{ install_dir }} app_path={{install_base }}/{{ install_dir }}
+
+- name: roadmap-django-migrate
+ django_manage: command=migrate virtualenv={{ install_base }}/virtualenv/{{ install_dir }} app_path={{install_base }}/{{ install_dir }}
+
+- name: roadmap-static-files
+ file: state=directory src={{ install_base }}/{{ install_dir }}/roadmap/static dest=/var/www/{{ install_dir }}/static/
+
+- name: roadmap-django-collectstatic
+ django_manage: command=collectstatic virtualenv={{ install_base }}/virtualenv/{{ install_dir }} app_path={{install_base }}/{{ install_dir }}
+
+# Make sure everything can be accessed by the Apache user.
+- name: fix-virtualenv-ownership
+ file: path={{ install_base }}/virtualenv recurse=yes owner={{ apache_user }} group={{ apache_user }}
+
+- name: fix-roadmap-install-ownership
+ file: path={{ install_base }}/{{ install_dir }} owner={{ apache_user }} group={{ apache_user }} recurse=yes
+
+- name: fix-roadmap-static-ownership
+ file: path=/var/www/{{ install_dir }} recurse=yes owner={{ apache_user }} group={{ apache_user }}
+
+- name: fix-roadmap-log-ownership
+ file: path=/var/log/roadmap recurse=yes owner={{ apache_user }} group={{ apache_user }}
diff --git a/ansible/roles/status/tasks/local_settings.yml b/ansible/roles/status/tasks/local_settings.yml
new file mode 100644
index 0000000..9790836
--- /dev/null
+++ b/ansible/roles/status/tasks/local_settings.yml
@@ -0,0 +1,6 @@
+# Create the local_settings file and fix its ownership.
+- name: local-settings
+ template: src=local_settings.py dest={{ install_base }}/{{ install_dir }}/linaroroadmap
+
+- name: fix-local-settings-ownership
+ file: path={{ install_base }}/{{ install_dir }}/linaroroadmap/local_settings.py owner={{ apache_user }} group={{ apache_user }}
diff --git a/ansible/roles/status/tasks/main.yml b/ansible/roles/status/tasks/main.yml
new file mode 100644
index 0000000..26bdebe
--- /dev/null
+++ b/ansible/roles/status/tasks/main.yml
@@ -0,0 +1,6 @@
+- include: clone_code.yml
+- include: local_settings.yml
+- include: install_roadmap.yml
+- include: wsgi.yml
+- include: apache_conf.yml
+- include: cronjobs.yml
diff --git a/ansible/roles/status/tasks/wsgi.yml b/ansible/roles/status/tasks/wsgi.yml
new file mode 100644
index 0000000..dd52c0d
--- /dev/null
+++ b/ansible/roles/status/tasks/wsgi.yml
@@ -0,0 +1,6 @@
+# Install the correct WSGI script.
+- name: install-wsgi
+ template: src=roadmap.wsgi dest={{ install_base }}/{{ install_dir }}/{{ install_dir }}.wsgi owner={{ apache_user }} group={{ apache_user }} mode=0744
+ tags:
+ - wsgi
+ notify: restart-apache
diff --git a/ansible/roles/status/templates/apache_production.conf b/ansible/roles/status/templates/apache_production.conf
new file mode 100644
index 0000000..bc3aa7e
--- /dev/null
+++ b/ansible/roles/status/templates/apache_production.conf
@@ -0,0 +1,56 @@
+<VirtualHost *:80>
+ ServerName {{ install_dir }}
+ ServerAdmin webmaster@linaro.org
+
+ Redirect permanent / https://{{ install_dir }}
+</VirtualHost>
+
+<VirtualHost *:443>
+ ServerName {{ install_dir }}
+ ServerAdmin webmaster@linaro.org
+
+ CustomLog ${APACHE_LOG_DIR}/{{ install_dir }}-access.log combined
+ ErrorLog ${APACHE_LOG_DIR}/{{ install_dir }}-error.log
+
+ SSLEngine on
+ SSLCertificateFile /etc/ssl/certs/{{ install_dir }}.crt
+ SSLCertificateKeyFile /etc/ssl/certs/{{ install_dir }}.key
+ SSLCACertificateFile /etc/ssl/certs/gd_bundle.crt
+
+ SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
+ #DocumentRoot /var/www/{{ install_dir }}
+ WSGIScriptAlias / {{ install_base }}/{{ install_dir }}/{{ install_dir }}.wsgi
+
+ ExpiresActive On
+ ExpiresDefault "access plus 300 seconds"
+
+ ExpiresByType text/css "access plus 1 month"
+ ExpiresByType text/javascript "access plus 1 month"
+ ExpiresByType image/png "access plus 1 month"
+ ExpiresByType image/jpg "access plus 1 month"
+ ExpiresByType image/jpeg "access plus 1 month"
+ ExpiresByType image/x-icon "access plus 1 month"
+
+ Header append Cache-Control "public, no-transform"
+
+ <FilesMatch "\.(html|htm)$">
+ Header add Cache-Control "must-revalidate"
+ </FilesMatch>
+
+ <FilesMatch "\.(js|css)$">
+ Header add Cache-Control "max-age=604800"
+ </FilesMatch>
+
+ Alias /static/ /var/www/{{ install_dir }}/static/
+ <Location "/static/">
+ Options -Indexes
+ SetOutputFilter DEFLATE
+
+ BrowserMatch ^Mozilla/4 gzip-only-text/html
+ BrowserMatch ^Mozilla/4\.0[678] no-gzip
+ BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
+
+ SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
+ Header append Vary User-Agent env=!dont-vary
+ </Location>
+</VirtualHost>
diff --git a/ansible/roles/status/templates/apache_staging.conf b/ansible/roles/status/templates/apache_staging.conf
new file mode 100644
index 0000000..68e1995
--- /dev/null
+++ b/ansible/roles/status/templates/apache_staging.conf
@@ -0,0 +1,47 @@
+<VirtualHost *:80>
+ ServerName {{ install_dir }}
+ ServerAdmin webmaster@linaro.org
+
+ CustomLog ${APACHE_LOG_DIR}/{{ install_dir }}-access.log combined
+ ErrorLog ${APACHE_LOG_DIR}/{{ install_dir }}-error.log
+
+ SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
+ #DocumentRoot /var/www/{{ install_dir }}
+
+ WSGIDaemonProcess {{ install_dir }} maximum-requests=10000
+ WSGIProcessGroup {{ install_dir }}
+ WSGIScriptAlias / {{ install_base }}/{{ install_dir }}/{{ install_dir }}.wsgi
+
+ ExpiresActive On
+ ExpiresDefault "access plus 300 seconds"
+
+ ExpiresByType text/css "access plus 1 month"
+ ExpiresByType text/javascript "access plus 1 month"
+ ExpiresByType image/png "access plus 1 month"
+ ExpiresByType image/jpg "access plus 1 month"
+ ExpiresByType image/jpeg "access plus 1 month"
+ ExpiresByType image/x-icon "access plus 1 month"
+
+ Header append Cache-Control "public, no-transform"
+
+ <FilesMatch "\.(html|htm)$">
+ Header add Cache-Control "must-revalidate"
+ </FilesMatch>
+
+ <FilesMatch "\.(js|css)$">
+ Header add Cache-Control "max-age=604800"
+ </FilesMatch>
+
+ Alias /static/ /var/www/{{ install_dir }}/static/
+ <Location "/static/">
+ Options -Indexes
+ SetOutputFilter DEFLATE
+
+ BrowserMatch ^Mozilla/4 gzip-only-text/html
+ BrowserMatch ^Mozilla/4\.0[678] no-gzip
+ BrowserMatch \bMSIE !no-gzip !gzip-only-text/html
+
+ SetEnvIfNoCase Request_URI \.(?:gif|jpe?g|png)$ no-gzip dont-vary
+ Header append Vary User-Agent env=!dont-vary
+ </Location>
+</VirtualHost>
diff --git a/ansible/roles/status/templates/apache_website.conf b/ansible/roles/status/templates/apache_website.conf
new file mode 100644
index 0000000..c6e763e
--- /dev/null
+++ b/ansible/roles/status/templates/apache_website.conf
@@ -0,0 +1,8 @@
+WSGIRestrictEmbedded On
+WSGILazyInitialization On
+
+{% if role == 'staging' %}
+{% extends "apache_staging.conf" %}
+{% else %}
+{% extends "apache_production.conf" %}
+{% endif %}
diff --git a/ansible/roles/status/templates/roadmap.wsgi b/ansible/roles/status/templates/roadmap.wsgi
new file mode 100644
index 0000000..dde7fd5
--- /dev/null
+++ b/ansible/roles/status/templates/roadmap.wsgi
@@ -0,0 +1,21 @@
+import os
+import sys
+import site
+
+# Add the site-packages of the chosen virtualenv to work with
+site.addsitedir('{{ install_base }}/virtualenv/{{ install_dir }}/'
+ 'local/lib/python2.7/site-packages')
+
+# Add the app's directory to the PYTHONPATH
+sys.path.append('{{ install_base }}/{{ install_dir }}/')
+sys.path.append('{{ install_base }}/{{ install_dir }}/linaroroadmap/')
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'linaroroadmap.settings'
+
+# Activate your virtual env
+activate_env = os.path.expanduser(
+ "{{ install_base }}/virtualenv/{{ install_dir }}/bin/activate_this.py")
+execfile(activate_env, dict(__file__=activate_env))
+
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()
diff --git a/ansible/roles/status/templates/roadmap_update_cron.sh b/ansible/roles/status/templates/roadmap_update_cron.sh
new file mode 100644
index 0000000..b4fdf7d
--- /dev/null
+++ b/ansible/roles/status/templates/roadmap_update_cron.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+# Copyright (C) 2013, 2014 Linaro
+#
+# This file is part of roadmap.
+#
+# roadmap is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# roadmap is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with roadmap. If not, see <http://www.gnu.org/licenses/>.
+
+export WORKON_HOME={{install_base }}/virtualenv
+source /usr/local/bin/virtualenvwrapper.sh
+
+workon {{ install_dir }}
+
+cd /srv/{{ install_dir }}
+
+{% if role == 'staging' %}
+./manage.py roadmap_import --debug
+./manage.py burndown_snapshot --debug
+{% else %}
+./manage.py roadmap_import
+./manage.py burndown_snapshot
+{% endif %} \ No newline at end of file
diff --git a/ansible/secrets.yml b/ansible/secrets.yml
new file mode 100644
index 0000000..31f12ca
--- /dev/null
+++ b/ansible/secrets.yml
@@ -0,0 +1,5 @@
+# Secrets the user need to insert.
+crowd_app_name:
+crowd_app_password:
+jira_username:
+jira_password:
diff --git a/ansible/site.yml b/ansible/site.yml
new file mode 100644
index 0000000..98e5f36
--- /dev/null
+++ b/ansible/site.yml
@@ -0,0 +1,11 @@
+# Install everything.
+
+- hosts: all
+ gather_facts: no
+ sudo: yes
+ roles:
+ - common
+ - status
+ vars_files:
+ - secrets.yml
+ \ No newline at end of file
diff --git a/bin/roadmap_expect.sh b/bin/roadmap_expect.sh
new file mode 100755
index 0000000..d2a3599
--- /dev/null
+++ b/bin/roadmap_expect.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/expect
+set timeout 11000
+log_file -noappend roadmap_sync.log;
+spawn ./roadmap_update.sh
+expect "JIRA password (roadmap-sync):"
+send ""
+expect eof
+exit
diff --git a/bin/roadmap_update.sh b/bin/roadmap_update.sh
new file mode 100755
index 0000000..06bef13
--- /dev/null
+++ b/bin/roadmap_update.sh
@@ -0,0 +1,27 @@
+#!/bin/bash
+# Copyright (C) 2013, 2014 Linaro
+#
+# This file is part of roadmap.
+#
+# roadmap is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# roadmap is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU Affero General Public License for more details.
+#
+# You should have received a copy of the GNU Affero General Public License
+# along with roadmap. If not, see <http://www.gnu.org/licenses/>.
+
+export WORKON_HOME=/srv/virtualenv
+source /usr/local/bin/virtualenvwrapper.sh
+
+workon roadmap
+
+cd /srv/production_roadmap
+
+./manage.py roadmap_import --debug
+./manage.py burndown_snapshot --debug
diff --git a/bin/wsgi_roadmap.wsgi b/bin/wsgi_roadmap.wsgi
new file mode 100644
index 0000000..cbe346b
--- /dev/null
+++ b/bin/wsgi_roadmap.wsgi
@@ -0,0 +1,20 @@
+import os
+import sys
+import site
+
+# Add the site-packages of the chosen virtualenv to work with
+site.addsitedir('/srv/virtualenv/roadmap/local/lib/python2.7/site-packages')
+
+# Add the app's directory to the PYTHONPATH
+sys.path.append('/srv/roadmap.linaro.org/')
+sys.path.append('/srv/roadmap.linaro.org/linaroroadmap/')
+
+os.environ['DJANGO_SETTINGS_MODULE'] = 'linaroroadmap.settings'
+
+# Activate your virtual env
+activate_env = os.path.expanduser(
+ "/srv/virtualenv/roadmap/bin/activate_this.py")
+execfile(activate_env, dict(__file__=activate_env))
+
+import django.core.handlers.wsgi
+application = django.core.handlers.wsgi.WSGIHandler()
diff --git a/linaroroadmap/local_settings.py.dev b/linaroroadmap/local_settings.py.dev
new file mode 100644
index 0000000..8b6d4ab
--- /dev/null
+++ b/linaroroadmap/local_settings.py.dev
@@ -0,0 +1,29 @@
+import random
+import string
+
+CROWD = {
+ 'url': '',
+ 'app_name': '',
+ 'password': '',
+ 'superuser': False,
+}
+
+JIRA_SERVER = None
+JIRA_USERNAME = None
+JIRA_PASSWORD = None
+
+JIRA_PROJECT = ''
+JIRA_STATUSES = []
+SFID = None
+
+AUTHENTICATION_BACKENDS = (
+ 'crowd.backends.CrowdBackend',
+)
+
+char_selection = string.ascii_letters + string.digits
+char_selection += '!@#$%^&*(-_=+)'
+
+SECRET_KEY = '{0}'.format(''.join(random.sample(char_selection, 50)))
+
+STATIC_ROOT = '/var/www/roadmap.linaro.org/static/'
+DEBUG = False
diff --git a/linaroroadmap/settings.py b/linaroroadmap/settings.py
index d18dd88..1d07e04 100644
--- a/linaroroadmap/settings.py
+++ b/linaroroadmap/settings.py
@@ -3,7 +3,7 @@
DEBUG = True
ADMINS = (
- # ('Your Name', 'your_email@example.com'),
+ ('linaro-infrastructure', 'linaro-infrastructure-errors@linaro.org')
)
AUTH_CROWD_ALWAYS_UPDATE_USER = True
@@ -19,29 +19,44 @@ AUTH_CROWD_SERVER_TRUSTED_ROOT_CERTS_FILE = None
TRUSTED_ADDRESS = []
-JIRA_PROJECT = ""
+CROWD = {
+ 'url': '',
+ 'app_name': '',
+ 'password': '',
+ 'superuser': False,
+}
+
+# The URL of the Jira server to connect to.
+JIRA_SERVER = None
+# The user name to use to connect to JIRA_SERVER.
+JIRA_USERNAME = None
+# The password for JIRA_SERVER.
+JIRA_PASSWORD = None
+
+JIRA_PROJECT = ''
JIRA_STATUSES = []
-SFID = None # custom field that defines relation between blueprint and engineering card in greenhopper
+# Custom field that defines relation between blueprint and engineering card
+# in greenhopper
+SFID = None
AUTHENTICATION_BACKENDS = (
#'django.contrib.auth.backends.ModelBackend',
'crowdrest.backend.CrowdRestBackend',
)
-MANAGERS = ADMINS
-
DATABASES = {
'default': {
- 'ENGINE': '', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'.
- 'NAME': '', # Or path to database file if using sqlite3.
- # The following settings are not used with sqlite3:
+ 'ENGINE': 'django.db.backends.sqlite3',
+ 'NAME': '/srv/production_roadmap/linaroroadmap.db',
'USER': '',
'PASSWORD': '',
- 'HOST': '', # Empty for localhost through domain sockets or '127.0.0.1' for localhost through TCP.
- 'PORT': '', # Set to empty string for default.
+ 'HOST': '',
+ 'PORT': '',
}
}
+MANAGERS = ADMINS
+
# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts
ALLOWED_HOSTS = ['*']
@@ -50,29 +65,20 @@ ALLOWED_HOSTS = ['*']
# 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'
+TIME_ZONE = 'Europe/London'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
-#USE_I18N = True
-#ugettext = lambda s: s
#LANGUAGES = (
# ('en-us', u'English'),
#)
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.
@@ -110,7 +116,7 @@ STATICFILES_FINDERS = (
)
# Make this unique, and don't share it with anybody.
-SECRET_KEY = 'd0jdu-gqrsj*8n@v!%x+s+!t-77y^$pz*s=ewebv0%c06wnyz-'
+SECRET_KEY = ''
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
@@ -183,11 +189,24 @@ LOGGING = {
'()': 'django.utils.log.RequireDebugFalse'
}
},
+ 'formatters': {
+ 'simple': {
+ 'format': '[%(asctime)s] %(levelname)-8s %(message)s',
+ }
+ },
'handlers': {
'mail_admins': {
'level': 'ERROR',
'filters': ['require_debug_false'],
'class': 'django.utils.log.AdminEmailHandler'
+ },
+ 'roadmap_to_file': {
+ 'level': 'DEBUG',
+ 'class': 'logging.handlers.TimedRotatingFileHandler',
+ 'filename': '/var/log/roadmap/roadmap.helpers.log',
+ 'backupCount': 5,
+ 'when': 'midnight',
+ 'formatter': 'simple'
}
},
'loggers': {
@@ -196,12 +215,31 @@ LOGGING = {
'level': 'ERROR',
'propagate': True,
},
+ 'roadmap.helpers': {
+ 'level': 'INFO',
+ 'handlers': ['roadmap_to_file'],
+ 'propagate': False,
+ }
}
}
try:
from local_settings import *
-except:
- print "local_settings not found"
+except ImportError:
+ import random
+ import string
+
+ # Create local_settings with random SECRET_KEY.
+ char_selection = string.ascii_letters + string.digits
+ char_selection_with_punctuation = char_selection + '!@#$%^&*(-_=+)'
+
+ # SECRET_KEY contains anything but whitespace
+ secret_key = ''.join(random.sample(char_selection_with_punctuation, 50))
+ local_settings_content = "SECRET_KEY = '{0}'\n".format(secret_key)
+
+ with open(os.path.join(PROJECT_ROOT, "local_settings.py"), "w") as f:
+ f.write(local_settings_content)
+
+ from local_settings import *
TEMPLATE_DEBUG = DEBUG
diff --git a/requirements.txt b/requirements.txt
index a9ca68e..c840714 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -4,4 +4,4 @@ simplejson==2.3.2
parsedatetime==1.1.2
South==0.8.2
django-reversion==1.7.1
--e git://git.linaro.org/people/pfalcon/django-crowd-rest-backend-linaro.git@8e599abd2aa1af210992f97f8dea5727e18a1928#egg=django_crowd_rest_backend-dev
+-e git://git.linaro.org/lava/django-crowd-rest-backend.git@c741f1738786c58253b400ffc27a16967702f84c#egg=django_crowd_rest_backend-dev
diff --git a/roadmap/helpers.py b/roadmap/helpers.py
index 0de4600..5bdf13c 100644
--- a/roadmap/helpers.py
+++ b/roadmap/helpers.py
@@ -15,12 +15,13 @@
# You should have received a copy of the GNU Affero General Public License
# along with roadmap. If not, see <http://www.gnu.org/licenses/>.
+import logging
+
from django.conf import settings
-from django.db.models import ManyToManyField, Count
+from django.db.models import ManyToManyField
from django.utils import timezone
from datetime import datetime
from operator import attrgetter
-from inspect import currentframe, getframeinfo
from roadmap.models import (
Burndown,
BurndownBar,
@@ -43,9 +44,11 @@ JIRA_VERIFY = 'jira_verify'
JIRA_DEPENDSON = 'dependson'
JIRA_IMPLEMENTS = 'implements'
JIRA_KEY = 'key'
-VERBOSE = 'verbose'
+DEBUG = 'debug'
SINGLE_UPDATE = 'single_update'
+log = logging.getLogger('roadmap.helpers')
+
class Issue(object):
@@ -59,9 +62,12 @@ class Issue(object):
def create_or_update(issue, options, jira=None):
+
if isinstance(issue, dict):
issue = Issue(issue)
+ log.debug("Creating or updating issue {0}".format(issue.key))
+
project = None
if hasattr(issue.fields, 'project') and issue.fields.project:
project, created = Project.objects.get_or_create(
@@ -88,22 +94,9 @@ def create_or_update(issue, options, jira=None):
cp, created = Component.objects.get_or_create(
name=comp.name, project=project)
components.append(cp.pk)
- if options[VERBOSE]:
- try:
- print "%s %s" % (issue.key, cp.name)
- except:
- frameinfo = getframeinfo(currentframe())
- print frameinfo.filename, frameinfo.lineno
- print "Error!"
+ log.debug("{0}: {1}".format(issue.key, cp.name))
else:
- if options[VERBOSE]:
- try:
- print "****** NO COMPONENT *******"
- print issue.key
- except:
- frameinfo = getframeinfo(currentframe())
- print frameinfo.filename, frameinfo.lineno
- print "Error!"
+ log.info("Issue {0} has no component".format(issue.key))
fixVersion = None
fixVersions = []
@@ -123,33 +116,19 @@ def create_or_update(issue, options, jira=None):
project=project
)
fixVersions.append(fv)
+
fixVersion = min(fixVersions, key=attrgetter('fix_date'))
- if options[VERBOSE]:
- try:
- print "fixVersion: %s" % (fixVersion)
- except:
- frameinfo = getframeinfo(currentframe())
- print frameinfo.filename, frameinfo.lineno
- print "Error!"
+ log.debug("Fix version is: {0}".format(fixVersion))
+
status = None
if hasattr(issue.fields, 'status') and issue.fields.status:
# find status in database or create one
status, created = Status.objects.get_or_create(
name=issue.fields.status.name, project=project)
- if options[VERBOSE]:
- try:
- print "status: %s" % (status)
- except:
- frameinfo = getframeinfo(currentframe())
- print frameinfo.filename, frameinfo.lineno
- print "Error!"
-
- key = issue.key
- url = options[JIRA_SERVER] + "/browse/" + issue.key
- summary = issue.fields.summary
+ log.debug("Status is: {0}".format(status))
+
cardStart = None
end = None
-
if fixVersion:
if fixVersion.name == "ONGOING":
if (hasattr(issue.fields, 'customfield_10300')
@@ -166,16 +145,9 @@ def create_or_update(issue, options, jira=None):
if cardStart:
break
else:
- # fixVersion is not ONGOING
cardStart = fixVersion.fix_date
+ log.debug("Start: {0} - End: {1}".format(cardStart, end))
- if options[VERBOSE]:
- try:
- print "start: {0}, end: {1}".format(cardStart, end)
- except:
- frameinfo = getframeinfo(currentframe())
- print frameinfo.filename, frameinfo.lineno
- print "Error!"
implementedby_list = []
depends_list = []
if hasattr(issue.fields, 'issuelinks'):
@@ -197,10 +169,10 @@ def create_or_update(issue, options, jira=None):
try:
implementedby_list.append(
Card.objects.get(key=link.inwardIssue.key))
- except:
- if options[VERBOSE]:
- # should not happen
- print "Card not found"
+ except Exception as ex:
+ log.error("Card not found")
+ log.exception(ex)
+
if link.type.name == "Depends" and options[JIRA_DEPENDSON]:
if hasattr(link, "inwardIssue"):
if jira:
@@ -218,56 +190,37 @@ def create_or_update(issue, options, jira=None):
try:
depends_list.append(
Card.objects.get(key=link.inwardIssue.key))
- except:
- if options[VERBOSE]:
- # should not happen
- print "Card not found"
+ except Exception as ex:
+ log.error("Card not found")
+ log.exception(ex)
+
blueprints = []
- if hasattr(issue.fields, 'issuetype') and issue.fields.issuetype.name == "Engineering card":
- if options[VERBOSE]:
- try:
- print "Engineering card: %s" % issue.key
- except:
- frameinfo = getframeinfo(currentframe())
- print frameinfo.filename, frameinfo.lineno
- print "Error!"
+ if (hasattr(issue.fields, 'issuetype') and
+ issue.fields.issuetype.name == "Engineering card"):
+ log.debug("Engineering card {0}".format(issue.key))
+
bp_search_string = "cf[%s] = %s" % (settings.SFID, issue.key)
bp_issues = jira.search_issues(bp_search_string)
start = 0
while len(bp_issues) > 0:
for index, blueprint in enumerate(bp_issues, start=1):
- if options[VERBOSE]:
- try:
- print "Processing BP %s/%s (%s/%s) %s" % (
- index + start,
- bp_issues.total,
- index,
- len(bp_issues),
- blueprint.key)
- except:
- frameinfo = getframeinfo(currentframe())
- print frameinfo.filename, frameinfo.lineno
- print "Error!"
+ log.debug("Processing Blueprint {0}/{1} ({2}/{3}) {4}".format(
+ index + start, bp_issues.total, index, len(bp_issues),
+ blueprint.key))
+
blueprints.append(
create_or_update(
- jira.issue(blueprint.key, expand="changelog"),
- options,
- jira).pk)
- start = start + len(bp_issues)
+ jira.issue(blueprint.key, expand="changelog"),
+ options, jira).pk)
+
+ start += len(bp_issues)
bp_issues = jira.search_issues(bp_search_string, startAt=start)
# compare implemented by and blueprints to eliminate duplicates
- implementedby_list = set(implementedby_list) | set(blueprints)
-
- if options[VERBOSE]:
- try:
- print "Dependencies"
- print depends_list
- print "Implements"
- print implementedby_list
- except:
- frameinfo = getframeinfo(currentframe())
- print frameinfo.filename, frameinfo.lineno
- print "Error!"
+ # implementedby_list = set(implementedby_list) | set(blueprints)
+ implementedby_list = list(set(implementedby_list).union(set(blueprints)))
+
+ log.debug("Dependencies: {0}".format(depends_list))
+ log.debug("Implements: {0}".format(implementedby_list))
label_list = []
if hasattr(issue.fields, 'labels'):
@@ -280,6 +233,9 @@ def create_or_update(issue, options, jira=None):
lbl.save()
label_list.append(lbl.pk)
+ url = settings.JIRA_SERVER + "/browse/" + issue.key
+ summary = issue.fields.summary
+
defaults = {
'url': url,
'summary': summary,
@@ -293,15 +249,10 @@ def create_or_update(issue, options, jira=None):
'card_type': card_type
}
- if options[VERBOSE]:
- try:
- print defaults
- except:
- frameinfo = getframeinfo(currentframe())
- print frameinfo.filename, frameinfo.lineno
- print "Error!"
+ log.debug("Default values: {0}".format(defaults))
- card, created = Card.objects.get_or_create(key=key, defaults=defaults)
+ card, created = Card.objects.get_or_create(
+ key=issue.key, defaults=defaults)
defaults.update({'components': components})
if options[JIRA_IMPLEMENTS]:
@@ -316,23 +267,14 @@ def create_or_update(issue, options, jira=None):
if isinstance(card._meta.get_field_by_name(attr)[0],
ManyToManyField):
if set([x.pk for x in attribute.all()]) != set(value):
- if options[VERBOSE]:
- try:
- print "{0} old: {1}, new: {2}".format(
- attr, [x.pk for x in attribute.all()], value)
- except:
- print "Error!"
+ log.debug("{0} old: {1}, new: {2}".format(
+ attr, [x.pk for x in attribute.all()], value))
setattr(card, attr, value)
else:
if attribute != value:
- if options[VERBOSE]:
- try:
- print "{0} old: {1}, new: {2}".format(
- attr, getattr(card, attr), value)
- except:
- print "Error!"
+ log.debug("{0} old: {1}, new: {2}".format(
+ attr, getattr(card, attr), value))
setattr(card, attr, value)
- card.save()
else:
card.components = components
if options[JIRA_IMPLEMENTS]:
@@ -340,31 +282,27 @@ def create_or_update(issue, options, jira=None):
if options[JIRA_DEPENDSON]:
card.dependson = depends_list
card.labels = label_list
- card.save()
- if options[VERBOSE]:
- try:
- print "Saving %s %s" % (key, summary)
- except:
- frameinfo = getframeinfo(currentframe())
- print frameinfo.filename, frameinfo.lineno
- print "Error!"
+
+ log.info("Saving card {0}".format(issue.key))
+ card.save()
+ log.debug("Saved {0} - {1}".format(issue.key, summary))
+
return card
-def get_card_blueprints(card, verbose=1):
+def get_card_blueprints(card):
blueprint_type = CardType.objects.get(name="Blueprint")
blueprints = []
for c in card.implementedby.all():
if c.card_type == blueprint_type:
blueprints.append(c)
- if verbose > 1:
- print "\t\t%s - %s" % (c.key, c.status.name)
+ log.debug("{0} - {1}".format(c.key, c.status.name))
else:
blueprints.extend(get_card_blueprints(c))
return blueprints
-def get_component_blueprints(component, snapshot_date, verbose=1):
+def get_component_blueprints(component, snapshot_date):
blueprint_type = CardType.objects.get(name="Blueprint")
blueprints = []
from_milestone = Milestone.objects.filter(
@@ -376,50 +314,48 @@ def get_component_blueprints(component, snapshot_date, verbose=1):
is_major=True).order_by("date")[0]
end_date = to_milestone.date
for card in component.card_set.filter(
- fix_version__fix_date__gt=start_date,
- fix_version__fix_date__lt=end_date):
- if verbose > 1:
- print "\t%s" % card.key
+ fix_version__fix_date__gt=start_date,
+ fix_version__fix_date__lt=end_date):
+
+ log.debug("{0}".format(card.key))
if card.card_type == blueprint_type:
- plueprints.append(card)
- if verbose > 1:
- print "\t\t%s" % card.key
+ blueprints.append(card)
else:
- blueprints.extend(get_card_blueprints(card, verbose))
+ blueprints.extend(get_card_blueprints(card))
return blueprints
-def collect_component_burndown(project, verbose=1):
+def collect_component_burndown(project):
components = Component.objects.filter(project=project)
- status_names = settings.JIRA_STATUSES
+
for component in components:
- if verbose > 1:
- print component.name
+ log.debug("Processing component {0}".format(component))
snapshot_date = timezone.make_aware(
datetime.now(),
timezone.get_default_timezone())
- blueprints = get_component_blueprints(component, snapshot_date, verbose)
+
+ blueprints = get_component_blueprints(component, snapshot_date)
burndown, created = Burndown.objects.get_or_create(component=component)
snapshot = BurndownSnapshot(burndown=burndown, date=snapshot_date)
snapshot.save()
- if verbose > 1:
- print "************* Snapshot **************"
- for name in status_names:
- if verbose > 1:
- print "\t%s" % name
+
+ for name in settings.JIRA_STATUSES:
+ log.debug("Analyzing status {0}".format(name))
bbar = BurndownBar(snapshot=snapshot, name=name, value=0)
+
for bp in blueprints:
if bp.status.name == name:
- if verbose > 1:
- print "\t\t%s" % bp.key
- bbar.value = bbar.value + 1
+ log.debug("Blueprint with status {0}: {1}".format(
+ name, bp.key))
+ bbar.value += 1
bbar.save()
-def collect_burndown(project, card_type, verbose=1):
+def collect_burndown(project, card_type):
snapshot_date = timezone.make_aware(
datetime.now(),
timezone.get_default_timezone())
+
blueprint_type = CardType.objects.get(name="Blueprint")
from_milestone = Milestone.objects.filter(
date__lte=snapshot_date,
@@ -430,9 +366,10 @@ def collect_burndown(project, card_type, verbose=1):
is_major=True).order_by("date")[0]
end_date = to_milestone.date
cards = Card.objects.filter(
- project=project,
- fix_version__fix_date__gt=start_date,
+ project=project,
+ fix_version__fix_date__gt=start_date,
fix_version__fix_date__lt=end_date)
+
blueprints = []
for card in cards:
if card.card_type == blueprint_type:
@@ -443,17 +380,14 @@ def collect_burndown(project, card_type, verbose=1):
burndown, created = Burndown.objects.get_or_create(project=project)
snapshot = BurndownSnapshot(burndown=burndown, date=snapshot_date)
snapshot.save()
- status_names = settings.JIRA_STATUSES
- if verbose > 1:
- print "************* Snapshot **************"
- for name in status_names:
- if verbose > 1:
- print "\t%s" % name
+
+ for name in settings.JIRA_STATUSES:
+ log.debug("Analyzing status {0}".format(name))
+
bbar = BurndownBar(snapshot=snapshot, name=name, value=0)
for bp in blueprints:
if bp.status.name == name:
- if verbose > 1:
- print "\t\t%s" % bp.key
- bbar.value = bbar.value + 1
+ log.debug("Blueprint with status {0}: {1}".format(
+ name, bp.key))
+ bbar.value += 1
bbar.save()
-
diff --git a/roadmap/management/commands/burndown_snapshot.py b/roadmap/management/commands/burndown_snapshot.py
index 90e8d8c..df13ea4 100644
--- a/roadmap/management/commands/burndown_snapshot.py
+++ b/roadmap/management/commands/burndown_snapshot.py
@@ -15,37 +15,57 @@
# You should have received a copy of the GNU Affero General Public License
# along with roadmap. If not, see <http://www.gnu.org/licenses/>.
-from django.core.management.base import BaseCommand, CommandError
+import logging
+
+from django.core.management.base import BaseCommand
from django.conf import settings
from optparse import make_option
-from roadmap.helpers import collect_burndown, collect_component_burndown
+from roadmap.helpers import (
+ collect_burndown,
+ collect_component_burndown,
+ DEBUG,
+)
from roadmap.models import Project, CardType
BURNDOWN_PROJECT = 'project'
BURNDOWN_CARDTYPE = 'cardtype'
+log = logging.getLogger("roadmap.helpers")
+
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
- make_option('--project',
+ make_option(
+ '--project',
dest=BURNDOWN_PROJECT,
action='store',
type='string',
help='Project name to create the burndown for',
default=settings.JIRA_PROJECT
),
- make_option('--card-type',
+ make_option(
+ '--card-type',
dest=BURNDOWN_CARDTYPE,
action='store',
type='string',
help='Jira issue type to aggregate the values for',
default='Blueprint'
),
+ make_option(
+ '--debug',
+ dest=DEBUG,
+ action='store_true',
+ default=False,
+ help='Enables debug logging. Default: false.'
+ ),
)
help = 'Collects the aggregate data to create the burndown bars'
def handle(self, *args, **options):
+ if options[DEBUG]:
+ log.setLevel(logging.DEBUG)
+
project = Project.objects.get(name=options[BURNDOWN_PROJECT])
card_type = CardType.objects.get(name=options[BURNDOWN_CARDTYPE])
- collect_burndown(project, card_type, int(options['verbosity']))
- collect_component_burndown(project, int(options['verbosity']))
+ collect_burndown(project, card_type)
+ collect_component_burndown(project)
diff --git a/roadmap/management/commands/roadmap_import.py b/roadmap/management/commands/roadmap_import.py
index bad3ef5..3c08b0e 100644
--- a/roadmap/management/commands/roadmap_import.py
+++ b/roadmap/management/commands/roadmap_import.py
@@ -1,4 +1,4 @@
-# Copyright (C) 2013 Linaro
+# Copyright (C) 2013, 2014 Linaro
#
# This file is part of roadmap.
#
@@ -15,121 +15,124 @@
# You should have received a copy of the GNU Affero General Public License
# along with roadmap. If not, see <http://www.gnu.org/licenses/>.
-from django.core.management.base import BaseCommand, CommandError
+import logging
+
+from django.core.management.base import (
+ BaseCommand,
+ CommandError
+)
from django.conf import settings
-from getpass import getpass
from jira.client import JIRA
from optparse import make_option
from roadmap.helpers import (
create_or_update,
- JIRA_SERVER,
- JIRA_USER,
JIRA_VERIFY,
JIRA_DEPENDSON,
JIRA_IMPLEMENTS,
JIRA_KEY,
SINGLE_UPDATE,
- VERBOSE
+ DEBUG
)
+log = logging.getLogger('roadmap.helpers')
+
class Command(BaseCommand):
option_list = BaseCommand.option_list + (
- make_option('--server',
- dest=JIRA_SERVER,
- action='store',
- type='string',
- help='Jira server URL'
- ),
- make_option('--username',
- dest=JIRA_USER,
- action='store',
- type='string',
- help='Jira username'
- ),
- make_option('--key',
+ make_option(
+ '--key',
dest=JIRA_KEY,
action='store',
type='string',
- help='Jira issue key. Only this issue is synced'
+ help="Jira issue key. Only this issue is synced"
),
- make_option('--no-implements',
+ make_option(
+ '--no-implements',
dest=JIRA_IMPLEMENTS,
action='store_false',
default=True,
- help='If set the "implements" relations are \
- not retrieved. Default: false'
+ help=("If set the 'implements' relations are not retrieved. "
+ "Default: false")
),
- make_option('--depends',
+ make_option(
+ '--depends',
dest=JIRA_DEPENDSON,
action='store_true',
default=False,
- help='If set the "depends" relations are not \
- retrieved. Default: false. Be careful when \
- not using --no-implements might run into infinite loop.'
+ help=("If set the 'depends' relations are not retrieved. Default: "
+ "false. Be careful when not using --no-implements might "
+ "run into infinite loop.")
),
- make_option('--no-verify',
+ make_option(
+ '--no-verify',
dest=JIRA_VERIFY,
action='store_false',
default=True,
- help='If set allows to connect to Jira server \
- secured with self-signed certificate'
+ help=("If set allows to connect to Jira server secured with "
+ "self-signed certificate")
),
- make_option('--verbose',
- dest=VERBOSE,
+ make_option(
+ '--debug',
+ dest=DEBUG,
action='store_true',
default=False,
- help='Enables verbose logging. Default: false.'
+ help='Enables debug logging. Default: false.'
),
-
)
help = 'Imports current snapshot from Jira server'
def handle(self, *args, **options):
- if options[JIRA_SERVER] == None:
- raise CommandError("Option `--server=...` must be specified.")
- if options[JIRA_USER] == None:
- raise CommandError("Option `--username=...` must be specified.")
- jira_password = getpass("JIRA password (%s):" % options[JIRA_USER])
+ if options[DEBUG]:
+ log.setLevel(logging.DEBUG)
+
+ if settings.JIRA_SERVER is None:
+ raise CommandError("Missing Jira server parameter.")
+ if settings.JIRA_USERNAME is None:
+ raise CommandError("Missing Jira username parameter.")
+ if settings.JIRA_PASSWORD is None:
+ raise CommandError("Missing Jira password parameter.")
+
jira_options = {
- 'server': options[JIRA_SERVER],
+ 'server': settings.JIRA_SERVER,
'verify': options[JIRA_VERIFY]}
jira = JIRA(
options=jira_options,
- basic_auth=(options[JIRA_USER], jira_password))
+ basic_auth=(settings.JIRA_USERNAME, settings.JIRA_PASSWORD)
+ )
+
start = 0
- if options[JIRA_KEY] == None:
+
+ if options[JIRA_KEY] is None:
issues = jira.search_issues(
- 'project="%s"' % settings.JIRA_PROJECT,
+ 'project="{0}"'.format(settings.JIRA_PROJECT),
startAt=start,
expand="changelog")
else:
issues = jira.search_issues(
- 'key="%s"' % options[JIRA_KEY],
+ 'key="{0}"'.format(options[JIRA_KEY]),
startAt=start,
expand="changelog")
options.update({SINGLE_UPDATE: False})
- print "Total: ", issues.total
+ log.info("Total issues: {0}".format(issues.total))
+
while len(issues) > 0:
- #allissues.extend(issues)
for index, issue in enumerate(issues, start=1):
- print "Processing %s/%s (%s/%s) %s" % (
- index + start,
- issues.total,
- index,
- len(issues),
- issue.key)
+ log.info("Processing issue {0}/{1} ({2}/{3}) {4}".format(
+ index+start, issues.total, index, len(issues), issue.key))
+
create_or_update(issue, options, jira)
- start = start + len(issues)
- if options[JIRA_KEY] == None:
+ start += len(issues)
+
+ log.info("Searching issues starting at {0}".format(start))
+ if options[JIRA_KEY] is None:
issues = jira.search_issues(
- 'project="%s"' % settings.JIRA_PROJECT,
+ 'project="{0}"'.format(settings.JIRA_PROJECT),
startAt=start,
expand="changelog")
else:
issues = jira.search_issues(
- 'key="%s"' % options[JIRA_KEY],
+ 'key="{0}"'.format(options[JIRA_KEY]),
startAt=start,
expand="changelog")
diff --git a/roadmap/models.py b/roadmap/models.py
index 15c2b3b..03411f4 100644
--- a/roadmap/models.py
+++ b/roadmap/models.py
@@ -42,7 +42,10 @@ class Burndown(models.Model):
component = models.ForeignKey('Component', null=True, blank=True)
def __unicode__(self):
- return self.project.name
+ ret_val = self.pk
+ if self.component:
+ ret_val = self.component.name
+ return ret_val
class BurndownSnapshot(models.Model):
diff --git a/roadmap/static/css/img/16/delete.png b/roadmap/static/css/img/16/delete.png
new file mode 100644
index 0000000..d54d0e0
--- /dev/null
+++ b/roadmap/static/css/img/16/delete.png
Binary files differ
diff --git a/roadmap/static/css/img/16/moveleft.png b/roadmap/static/css/img/16/moveleft.png
new file mode 100644
index 0000000..44c0d82
--- /dev/null
+++ b/roadmap/static/css/img/16/moveleft.png
Binary files differ
diff --git a/roadmap/static/css/img/16/moveright.png b/roadmap/static/css/img/16/moveright.png
new file mode 100644
index 0000000..9d39bde
--- /dev/null
+++ b/roadmap/static/css/img/16/moveright.png
Binary files differ
diff --git a/roadmap/static/css/img/16/new.png b/roadmap/static/css/img/16/new.png
new file mode 100644
index 0000000..7eeba13
--- /dev/null
+++ b/roadmap/static/css/img/16/new.png
Binary files differ
diff --git a/roadmap/static/css/img/16/zoomin.png b/roadmap/static/css/img/16/zoomin.png
new file mode 100644
index 0000000..b68dfad
--- /dev/null
+++ b/roadmap/static/css/img/16/zoomin.png
Binary files differ
diff --git a/roadmap/static/css/img/16/zoomout.png b/roadmap/static/css/img/16/zoomout.png
new file mode 100644
index 0000000..64e3727
--- /dev/null
+++ b/roadmap/static/css/img/16/zoomout.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png b/roadmap/static/css/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png
index 5b5dab2..5b5dab2 100755..100644
--- a/roadmap/static/css/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png
+++ b/roadmap/static/css/redmond/images/ui-bg_flat_0_aaaaaa_40x100.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-bg_flat_55_fbec88_40x100.png b/roadmap/static/css/redmond/images/ui-bg_flat_55_fbec88_40x100.png
index 47acaad..47acaad 100755..100644
--- a/roadmap/static/css/redmond/images/ui-bg_flat_55_fbec88_40x100.png
+++ b/roadmap/static/css/redmond/images/ui-bg_flat_55_fbec88_40x100.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png b/roadmap/static/css/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png
index 9fb564f..9fb564f 100755..100644
--- a/roadmap/static/css/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png
+++ b/roadmap/static/css/redmond/images/ui-bg_glass_75_d0e5f5_1x400.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-bg_glass_85_dfeffc_1x400.png b/roadmap/static/css/redmond/images/ui-bg_glass_85_dfeffc_1x400.png
index 0149515..0149515 100755..100644
--- a/roadmap/static/css/redmond/images/ui-bg_glass_85_dfeffc_1x400.png
+++ b/roadmap/static/css/redmond/images/ui-bg_glass_85_dfeffc_1x400.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-bg_glass_95_fef1ec_1x400.png b/roadmap/static/css/redmond/images/ui-bg_glass_95_fef1ec_1x400.png
index 4443fdc..4443fdc 100755..100644
--- a/roadmap/static/css/redmond/images/ui-bg_glass_95_fef1ec_1x400.png
+++ b/roadmap/static/css/redmond/images/ui-bg_glass_95_fef1ec_1x400.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png b/roadmap/static/css/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png
index 81ecc36..81ecc36 100755..100644
--- a/roadmap/static/css/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png
+++ b/roadmap/static/css/redmond/images/ui-bg_gloss-wave_55_5c9ccc_500x100.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png b/roadmap/static/css/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png
index 4f3faf8..4f3faf8 100755..100644
--- a/roadmap/static/css/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png
+++ b/roadmap/static/css/redmond/images/ui-bg_inset-hard_100_f5f8f9_1x100.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png b/roadmap/static/css/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png
index 38c3833..38c3833 100755..100644
--- a/roadmap/static/css/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png
+++ b/roadmap/static/css/redmond/images/ui-bg_inset-hard_100_fcfdfd_1x100.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-icons_217bc0_256x240.png b/roadmap/static/css/redmond/images/ui-icons_217bc0_256x240.png
index 9c88458..9c88458 100755..100644
--- a/roadmap/static/css/redmond/images/ui-icons_217bc0_256x240.png
+++ b/roadmap/static/css/redmond/images/ui-icons_217bc0_256x240.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-icons_2e83ff_256x240.png b/roadmap/static/css/redmond/images/ui-icons_2e83ff_256x240.png
index b425c44..b425c44 100755..100644
--- a/roadmap/static/css/redmond/images/ui-icons_2e83ff_256x240.png
+++ b/roadmap/static/css/redmond/images/ui-icons_2e83ff_256x240.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-icons_469bdd_256x240.png b/roadmap/static/css/redmond/images/ui-icons_469bdd_256x240.png
index 5e7915f..5e7915f 100755..100644
--- a/roadmap/static/css/redmond/images/ui-icons_469bdd_256x240.png
+++ b/roadmap/static/css/redmond/images/ui-icons_469bdd_256x240.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-icons_6da8d5_256x240.png b/roadmap/static/css/redmond/images/ui-icons_6da8d5_256x240.png
index 60e20ca..60e20ca 100755..100644
--- a/roadmap/static/css/redmond/images/ui-icons_6da8d5_256x240.png
+++ b/roadmap/static/css/redmond/images/ui-icons_6da8d5_256x240.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-icons_cd0a0a_256x240.png b/roadmap/static/css/redmond/images/ui-icons_cd0a0a_256x240.png
index 2db88b7..2db88b7 100755..100644
--- a/roadmap/static/css/redmond/images/ui-icons_cd0a0a_256x240.png
+++ b/roadmap/static/css/redmond/images/ui-icons_cd0a0a_256x240.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-icons_d8e7f3_256x240.png b/roadmap/static/css/redmond/images/ui-icons_d8e7f3_256x240.png
index 2c8aac4..2c8aac4 100755..100644
--- a/roadmap/static/css/redmond/images/ui-icons_d8e7f3_256x240.png
+++ b/roadmap/static/css/redmond/images/ui-icons_d8e7f3_256x240.png
Binary files differ
diff --git a/roadmap/static/css/redmond/images/ui-icons_f9bd01_256x240.png b/roadmap/static/css/redmond/images/ui-icons_f9bd01_256x240.png
index e81603f..e81603f 100755..100644
--- a/roadmap/static/css/redmond/images/ui-icons_f9bd01_256x240.png
+++ b/roadmap/static/css/redmond/images/ui-icons_f9bd01_256x240.png
Binary files differ
diff --git a/roadmap/static/css/redmond/jquery-ui-1.7.1.custom.css b/roadmap/static/css/redmond/jquery-ui-1.7.1.custom.css
index 3e15a2e..3e15a2e 100755..100644
--- a/roadmap/static/css/redmond/jquery-ui-1.7.1.custom.css
+++ b/roadmap/static/css/redmond/jquery-ui-1.7.1.custom.css
diff --git a/roadmap/static/css/ui.slider.extras.css b/roadmap/static/css/ui.slider.extras.css
index 862388b..862388b 100755..100644
--- a/roadmap/static/css/ui.slider.extras.css
+++ b/roadmap/static/css/ui.slider.extras.css
diff --git a/roadmap/static/js/selectToUISlider.jQuery.js b/roadmap/static/js/selectToUISlider.jQuery.js
index 18704d7..18704d7 100755..100644
--- a/roadmap/static/js/selectToUISlider.jQuery.js
+++ b/roadmap/static/js/selectToUISlider.jQuery.js
diff --git a/roadmap/templates/roadmap/component.html b/roadmap/templates/roadmap/component.html
index 9ec8039..4797f54 100644
--- a/roadmap/templates/roadmap/component.html
+++ b/roadmap/templates/roadmap/component.html
@@ -16,7 +16,6 @@
<script type="text/javascript" src="{{ STATIC_URL }}js/highcharts.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/modules/exporting.js"></script>
<script type="text/javascript" src="{{ STATIC_URL }}js/ui/jquery-ui.js"></script>
-<script type="text/javascript" src="{{ STATIC_URL }}js/ajax.js"></script>
<!-- status chart -->
<script type="text/javascript">
$(function () {
diff --git a/roadmap/templates/roadmap/roadmap.html b/roadmap/templates/roadmap/roadmap.html
index 4a16563..0b1abba 100644
--- a/roadmap/templates/roadmap/roadmap.html
+++ b/roadmap/templates/roadmap/roadmap.html
@@ -14,7 +14,7 @@
<div class="concept" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Drafting</div><div class="timeline-event timeline-event-dot drafting" style="position: absolute;"></div></div>
<div class="approved" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Approved</div><div class="timeline-event timeline-event-dot approved" style="position: absolute;"></div></div>
<div class="scheduled" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Scheduled</div><div class="timeline-event timeline-event-dot scheduled" style="position: absolute;"></div></div>
- <div class="development" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Development</div><div class="timeline-event timeline-event-dot development" style="position: absolute;"></div></div>
+ <div class="development" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Development</div><div class="timeline-event timeline-event-dot engineering" style="position: absolute;"></div></div>
<div class="released" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Closed</div><div class="timeline-event timeline-event-dot closed" style="position: absolute;"></div></div>
</div>
diff --git a/roadmap/templates/roadmap/roadmap_index.html b/roadmap/templates/roadmap/roadmap_index.html
new file mode 100644
index 0000000..c3cda71
--- /dev/null
+++ b/roadmap/templates/roadmap/roadmap_index.html
@@ -0,0 +1,21 @@
+{% extends 'roadmap/base.html' %}
+
+{% block title %}Linaro Roadmap{% endblock %}
+
+{% block headertitle %}Linaro Roadmap{% endblock %}
+
+{% block main_content %}
+
+ <div style="margin: 10px;">
+ <table class="roadmaptable" width="100%">
+ {% for component in components %}
+ <tr class="{% if forloop.counter|divisibleby:2 %}even{% else %}odd{% endif %}">
+ <td><a href="/component/{{ component.pk }}/roadmap">{{ component.name }}</a></td>
+ </tr>
+ {% endfor %}
+ </table>
+ </div>
+
+{% include "roadmap/help.html" %}
+
+{% endblock %}
diff --git a/roadmap/templates/roadmap/timeline.html b/roadmap/templates/roadmap/timeline.html
index 09226a7..0e1d714 100644
--- a/roadmap/templates/roadmap/timeline.html
+++ b/roadmap/templates/roadmap/timeline.html
@@ -14,7 +14,7 @@
<div class="concept" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Drafting</div><div class="timeline-event timeline-event-dot drafting" style="position: absolute;"></div></div>
<div class="approved" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Approved</div><div class="timeline-event timeline-event-dot approved" style="position: absolute;"></div></div>
<div class="scheduled" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Scheduled</div><div class="timeline-event timeline-event-dot scheduled" style="position: absolute;"></div></div>
- <div class="development" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Development</div><div class="timeline-event timeline-event-dot development" style="position: absolute;"></div></div>
+ <div class="development" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Development</div><div class="timeline-event timeline-event-dot engineering" style="position: absolute;"></div></div>
<div class="released" style="position: relative; float:left; margin-left: 20px;"><div class="timeline-event-content">Closed</div><div class="timeline-event timeline-event-dot closed" style="position: absolute;"></div></div>
</div>
diff --git a/roadmap/urls.py b/roadmap/urls.py
index 16a23f4..c4ca46e 100644
--- a/roadmap/urls.py
+++ b/roadmap/urls.py
@@ -26,12 +26,14 @@ from roadmap.views import (
status,
search,
update_card,
- index
+ index,
+ roadmap_index,
)
urlpatterns = patterns(
'',
url(r'^$', index, name="index"),
+ url(r'^roadmap/$', roadmap_index),
url(r'^roadmap/(?P<roadmap_id>\d+)$', roadmap),
url(r'^component/(?P<roadmap_id>\d+)$', component),
url(r'^status/(?P<status_id>\d+)$', status),
diff --git a/roadmap/views.py b/roadmap/views.py
index df27888..a32bb10 100644
--- a/roadmap/views.py
+++ b/roadmap/views.py
@@ -18,7 +18,6 @@
import json
import sys
import traceback
-import reversion
from datetime import (
datetime,
@@ -29,13 +28,12 @@ from django.conf import settings
from django.contrib.auth.decorators import login_required
from django.db.models import Max, Min
from django.http import HttpResponse, HttpResponseServerError
-from django.shortcuts import redirect
from django.template import RequestContext, loader
from django.views.decorators.csrf import csrf_exempt
-from roadmap.forms import RoadmapForm, LabelForm
+from roadmap.forms import LabelForm
from roadmap.helpers import (
- VERBOSE,
+ DEBUG,
SINGLE_UPDATE,
JIRA_SERVER,
JIRA_IMPLEMENTS,
@@ -47,7 +45,6 @@ from roadmap.models import (
Card,
Component,
Status,
- RoadmapRelease,
Project,
Label,
Milestone,
@@ -380,7 +377,7 @@ def update_card(request):
card_json = json.loads(request.body)
jira_server_parts = card_json['issue']['self'].split("/")
options = {
- VERBOSE: settings.DEBUG,
+ DEBUG: settings.DEBUG,
JIRA_SERVER: jira_server_parts[0] + "//" + jira_server_parts[2],
JIRA_IMPLEMENTS: True,
JIRA_DEPENDSON: True,
@@ -394,3 +391,18 @@ def update_card(request):
return HttpResponseServerError()
return HttpResponse()
+
+
+@login_required
+def roadmap_index(request):
+ template = loader.get_template('roadmap/roadmap_index.html')
+
+ project = Project.objects.get(name=settings.JIRA_PROJECT)
+ components = Component.objects.filter(project=project).order_by("name")
+
+ context = RequestContext(request, {
+ 'project': project,
+ 'components': components,
+ })
+
+ return HttpResponse(template.render(context))