summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--pharos-dashboard/docker-compose.yml2
-rw-r--r--pharos-dashboard/src/account/jira_util.py3
-rw-r--r--pharos-dashboard/src/account/migrations/0002_auto_20161005_1201.py25
-rw-r--r--pharos-dashboard/src/account/models.py11
-rw-r--r--pharos-dashboard/src/account/tasks.py34
-rw-r--r--pharos-dashboard/src/account/tests/test_general.py10
-rw-r--r--pharos-dashboard/src/account/views.py41
-rw-r--r--pharos-dashboard/src/api/urls.py1
-rw-r--r--pharos-dashboard/src/api/views.py18
-rw-r--r--pharos-dashboard/src/booking/admin.py6
-rw-r--r--pharos-dashboard/src/booking/forms.py8
-rw-r--r--pharos-dashboard/src/booking/migrations/0002_auto_20161005_1202.py40
-rw-r--r--pharos-dashboard/src/booking/models.py34
-rw-r--r--pharos-dashboard/src/booking/tests/test_models.py29
-rw-r--r--pharos-dashboard/src/booking/tests/test_views.py37
-rw-r--r--pharos-dashboard/src/booking/urls.py2
-rw-r--r--pharos-dashboard/src/booking/views.py45
-rw-r--r--pharos-dashboard/src/dashboard/migrations/0002_auto_20161005_1202.py21
-rw-r--r--pharos-dashboard/src/dashboard/models.py4
-rw-r--r--pharos-dashboard/src/dashboard/tasks.py3
-rw-r--r--pharos-dashboard/src/dashboard/templatetags/jira_filters.py3
-rw-r--r--pharos-dashboard/src/dashboard/tests/__init__.py10
-rw-r--r--pharos-dashboard/src/dashboard/tests/test_models.py69
-rw-r--r--pharos-dashboard/src/dashboard/tests/test_views.py75
-rw-r--r--pharos-dashboard/src/dashboard/urls.py1
-rw-r--r--pharos-dashboard/src/dashboard/views.py10
-rw-r--r--pharos-dashboard/src/jenkins/adapter.py2
-rw-r--r--pharos-dashboard/src/jenkins/models.py2
-rw-r--r--pharos-dashboard/src/jenkins/tasks.py15
-rw-r--r--pharos-dashboard/src/jenkins/tests.py77
-rw-r--r--pharos-dashboard/src/notification/models.py5
-rw-r--r--pharos-dashboard/src/notification/tasks.py2
-rw-r--r--pharos-dashboard/src/notification/tests.py41
-rw-r--r--pharos-dashboard/src/static/js/booking-calendar.js28
-rw-r--r--pharos-dashboard/src/templates/account/user_list.html20
-rw-r--r--pharos-dashboard/src/templates/account/userprofile_update_form.html10
-rw-r--r--pharos-dashboard/src/templates/base.html9
-rw-r--r--pharos-dashboard/src/templates/booking/booking_calendar.html51
-rw-r--r--pharos-dashboard/src/templates/booking/booking_detail.html8
-rw-r--r--pharos-dashboard/src/templates/booking/booking_list.html50
-rw-r--r--pharos-dashboard/src/templates/booking/booking_table.html10
-rw-r--r--pharos-dashboard/src/templates/dashboard/resource_detail.html1
42 files changed, 733 insertions, 140 deletions
diff --git a/pharos-dashboard/docker-compose.yml b/pharos-dashboard/docker-compose.yml
index 00a8d15..b487620 100644
--- a/pharos-dashboard/docker-compose.yml
+++ b/pharos-dashboard/docker-compose.yml
@@ -59,7 +59,7 @@ services:
worker:
restart: always
build: ./worker/
- command: bash -c "celery -A pharos_dashboard worker -l info -B"
+ command: bash -c "celery -A pharos_dashboard worker -l info -B --schedule=~/celerybeat-schedule""
env_file: config.env
links:
- postgres
diff --git a/pharos-dashboard/src/account/jira_util.py b/pharos-dashboard/src/account/jira_util.py
index c333f8c..fdb87f7 100644
--- a/pharos-dashboard/src/account/jira_util.py
+++ b/pharos-dashboard/src/account/jira_util.py
@@ -12,11 +12,10 @@ import base64
import os
import oauth2 as oauth
+from django.conf import settings
from jira import JIRA
from tlslite.utils import keyfactory
-from django.conf import settings
-
class SignatureMethod_RSA_SHA1(oauth.SignatureMethod):
name = 'RSA-SHA1'
diff --git a/pharos-dashboard/src/account/migrations/0002_auto_20161005_1201.py b/pharos-dashboard/src/account/migrations/0002_auto_20161005_1201.py
new file mode 100644
index 0000000..33d2cc5
--- /dev/null
+++ b/pharos-dashboard/src/account/migrations/0002_auto_20161005_1201.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-10-05 12:01
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='userprofile',
+ name='full_name',
+ field=models.CharField(default='', max_length=100),
+ ),
+ migrations.AddField(
+ model_name='userprofile',
+ name='jira_url',
+ field=models.CharField(default='', max_length=100),
+ ),
+ ]
diff --git a/pharos-dashboard/src/account/models.py b/pharos-dashboard/src/account/models.py
index 621f669..c2e9902 100644
--- a/pharos-dashboard/src/account/models.py
+++ b/pharos-dashboard/src/account/models.py
@@ -8,11 +8,9 @@
##############################################################################
-from django.db import models
-
from django.contrib.auth.models import User
+from django.db import models
-from dashboard.models import Resource
def upload_to(object, filename):
return object.user.username + '/' + filename
@@ -23,8 +21,15 @@ class UserProfile(models.Model):
ssh_public_key = models.FileField(upload_to=upload_to, null=True, blank=True)
pgp_public_key = models.FileField(upload_to=upload_to, null=True, blank=True)
company = models.CharField(max_length=200, blank=False)
+
oauth_token = models.CharField(max_length=1024, blank=False)
oauth_secret = models.CharField(max_length=1024, blank=False)
+ jira_url = models.CharField(max_length=100, default='')
+ full_name = models.CharField(max_length=100, default='')
+
class Meta:
db_table = 'user_profile'
+
+ def __str__(self):
+ return self.user.username
diff --git a/pharos-dashboard/src/account/tasks.py b/pharos-dashboard/src/account/tasks.py
new file mode 100644
index 0000000..bfb865d
--- /dev/null
+++ b/pharos-dashboard/src/account/tasks.py
@@ -0,0 +1,34 @@
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from celery import shared_task
+from django.contrib.auth.models import User
+from jira import JIRAError
+
+from account.jira_util import get_jira
+
+
+@shared_task
+def sync_jira_accounts():
+ users = User.objects.all()
+ for user in users:
+ jira = get_jira(user)
+ try:
+ user_dict = jira.myself()
+ except JIRAError:
+ # User can be anonymous (local django admin account)
+ continue
+ user.email = user_dict['emailAddress']
+ user.userprofile.url = user_dict['self']
+ user.userprofile.full_name = user_dict['displayName']
+ print(user_dict)
+
+ user.userprofile.save()
+ user.save() \ No newline at end of file
diff --git a/pharos-dashboard/src/account/tests/test_general.py b/pharos-dashboard/src/account/tests/test_general.py
index 72e7ea1..e8f483b 100644
--- a/pharos-dashboard/src/account/tests/test_general.py
+++ b/pharos-dashboard/src/account/tests/test_general.py
@@ -48,3 +48,13 @@ class AccountMiddlewareTestCase(TestCase):
self.user1profile.save()
self.client.get(url)
self.assertEqual(timezone.get_current_timezone_name(), 'Etc/Greenwich')
+
+ # if there is no profile for a user, it should be created
+ user2 = User.objects.create(username='user2')
+ user2.set_password('user2')
+ user2.save()
+ self.client.login(username='user2', password='user2')
+ self.client.get(url)
+ self.assertTrue(user2.userprofile)
+
+
diff --git a/pharos-dashboard/src/account/views.py b/pharos-dashboard/src/account/views.py
index 3b4269d..17fbdc3 100644
--- a/pharos-dashboard/src/account/views.py
+++ b/pharos-dashboard/src/account/views.py
@@ -12,6 +12,7 @@ import os
import urllib
import oauth2 as oauth
+from django.conf import settings
from django.contrib import messages
from django.contrib.auth import logout, authenticate, login
from django.contrib.auth.decorators import login_required
@@ -19,17 +20,13 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.urls import reverse
from django.utils.decorators import method_decorator
-from django.views.generic import RedirectView
-from django.views.generic import TemplateView
-from django.views.generic import UpdateView
+from django.views.generic import RedirectView, TemplateView, UpdateView
from jira import JIRA
+from rest_framework.authtoken.models import Token
from account.forms import AccountSettingsForm
from account.jira_util import SignatureMethod_RSA_SHA1
from account.models import UserProfile
-from django.conf import settings
-
-consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET)
@method_decorator(login_required, name='dispatch')
@@ -46,22 +43,36 @@ class AccountSettingsView(UpdateView):
def get_object(self, queryset=None):
return self.request.user.userprofile
+ def get_context_data(self, **kwargs):
+ token, created = Token.objects.get_or_create(user=self.request.user)
+ context = super(AccountSettingsView, self).get_context_data(**kwargs)
+ context.update({'title': "Settings", 'token': token})
+ return context
+
class JiraLoginView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
+ consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET)
client = oauth.Client(consumer)
client.set_signature_method(SignatureMethod_RSA_SHA1())
# Step 1. Get a request token from Jira.
- resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST")
+ try:
+ resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST")
+ except Exception as e:
+ messages.add_message(self.request, messages.ERROR,
+ 'Error: Connection to Jira failed. Please contact an Administrator')
+ return '/'
if resp['status'] != '200':
- raise Exception("Invalid response %s: %s" % (resp['status'], content))
+ messages.add_message(self.request, messages.ERROR,
+ 'Error: Connection to Jira failed. Please contact an Administrator')
+ return '/'
# Step 2. Store the request token in a session for later use.
self.request.session['request_token'] = dict(urllib.parse.parse_qsl(content.decode()))
# Step 3. Redirect the user to the authentication URL.
url = settings.OAUTH_AUTHORIZE_URL + '?oauth_token=' + \
- self.request.session['request_token']['oauth_token'] + \
+ self.request.session['request_token']['oauth_token'] + \
'&oauth_callback=' + settings.OAUTH_CALLBACK_URL
return url
@@ -75,14 +86,22 @@ class JiraLogoutView(LoginRequiredMixin, RedirectView):
class JiraAuthenticatedView(RedirectView):
def get_redirect_url(self, *args, **kwargs):
# Step 1. Use the request token in the session to build a new client.
+ consumer = oauth.Consumer(settings.OAUTH_CONSUMER_KEY, settings.OAUTH_CONSUMER_SECRET)
token = oauth.Token(self.request.session['request_token']['oauth_token'],
self.request.session['request_token']['oauth_token_secret'])
client = oauth.Client(consumer, token)
client.set_signature_method(SignatureMethod_RSA_SHA1())
# Step 2. Request the authorized access token from Jira.
- resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST")
+ try:
+ resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST")
+ except Exception as e:
+ messages.add_message(self.request, messages.ERROR,
+ 'Error: Connection to Jira failed. Please contact an Administrator')
+ return '/'
if resp['status'] != '200':
+ messages.add_message(self.request, messages.ERROR,
+ 'Error: Connection to Jira failed. Please contact an Administrator')
return '/'
access_token = dict(urllib.parse.parse_qsl(content.decode()))
@@ -122,6 +141,8 @@ class JiraAuthenticatedView(RedirectView):
# redirect user to settings page to complete profile
return url
+
+@method_decorator(login_required, name='dispatch')
class UserListView(TemplateView):
template_name = "account/user_list.html"
diff --git a/pharos-dashboard/src/api/urls.py b/pharos-dashboard/src/api/urls.py
index 5206ac7..dfbe1ac 100644
--- a/pharos-dashboard/src/api/urls.py
+++ b/pharos-dashboard/src/api/urls.py
@@ -35,4 +35,5 @@ router.register(r'bookings', BookingViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
+ url(r'^token$', GenerateTokenView.as_view(), name='generate_token'),
] \ No newline at end of file
diff --git a/pharos-dashboard/src/api/views.py b/pharos-dashboard/src/api/views.py
index 761ce6e..2595e5e 100644
--- a/pharos-dashboard/src/api/views.py
+++ b/pharos-dashboard/src/api/views.py
@@ -8,7 +8,12 @@
##############################################################################
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect
+from django.utils.decorators import method_decorator
+from django.views import View
from rest_framework import viewsets
+from rest_framework.authtoken.models import Token
from api.serializers import ResourceSerializer, ServerSerializer, BookingSerializer
from booking.models import Booking
@@ -30,4 +35,15 @@ class ServerViewSet(viewsets.ModelViewSet):
class ResourceViewSet(viewsets.ModelViewSet):
queryset = Resource.objects.all()
serializer_class = ResourceSerializer
- filter_fields = ('name',) \ No newline at end of file
+ filter_fields = ('name',)
+
+
+@method_decorator(login_required, name='dispatch')
+class GenerateTokenView(View):
+ def get(self, request, *args, **kwargs):
+ user = self.request.user
+ token, created = Token.objects.get_or_create(user=user)
+ if not created:
+ token.delete()
+ Token.objects.create(user=user)
+ return redirect('account:settings')
diff --git a/pharos-dashboard/src/booking/admin.py b/pharos-dashboard/src/booking/admin.py
index 7a7f251..d883be1 100644
--- a/pharos-dashboard/src/booking/admin.py
+++ b/pharos-dashboard/src/booking/admin.py
@@ -10,6 +10,8 @@
from django.contrib import admin
-from booking.models import Booking
+from booking.models import *
-admin.site.register(Booking) \ No newline at end of file
+admin.site.register(Booking)
+admin.site.register(Installer)
+admin.site.register(Scenario) \ No newline at end of file
diff --git a/pharos-dashboard/src/booking/forms.py b/pharos-dashboard/src/booking/forms.py
index 02ac887..2dbfacb 100644
--- a/pharos-dashboard/src/booking/forms.py
+++ b/pharos-dashboard/src/booking/forms.py
@@ -10,10 +10,14 @@
import django.forms as forms
+from booking.models import Installer, Scenario
+
class BookingForm(forms.Form):
- fields = ['start', 'end', 'purpose']
+ fields = ['start', 'end', 'purpose', 'installer', 'scenario']
start = forms.DateTimeField()
end = forms.DateTimeField()
- purpose = forms.CharField(max_length=300) \ No newline at end of file
+ purpose = forms.CharField(max_length=300)
+ installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=False)
+ scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=False) \ No newline at end of file
diff --git a/pharos-dashboard/src/booking/migrations/0002_auto_20161005_1202.py b/pharos-dashboard/src/booking/migrations/0002_auto_20161005_1202.py
new file mode 100644
index 0000000..05684f0
--- /dev/null
+++ b/pharos-dashboard/src/booking/migrations/0002_auto_20161005_1202.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-10-05 12:02
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('booking', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='Installer',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('name', models.CharField(max_length=30)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='Scenario',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('name', models.CharField(max_length=300)),
+ ],
+ ),
+ migrations.AddField(
+ model_name='booking',
+ name='installer',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='booking.Installer'),
+ ),
+ migrations.AddField(
+ model_name='booking',
+ name='scenario',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='booking.Scenario'),
+ ),
+ ]
diff --git a/pharos-dashboard/src/booking/models.py b/pharos-dashboard/src/booking/models.py
index 200dc83..0b3fa3b 100644
--- a/pharos-dashboard/src/booking/models.py
+++ b/pharos-dashboard/src/booking/models.py
@@ -8,13 +8,28 @@
##############################################################################
+from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from jira import JIRA
from jira import JIRAError
from dashboard.models import Resource
-from django.conf import settings
+
+
+class Installer(models.Model):
+ id = models.AutoField(primary_key=True)
+ name = models.CharField(max_length=30)
+
+ def __str__(self):
+ return self.name
+
+class Scenario(models.Model):
+ id = models.AutoField(primary_key=True)
+ name = models.CharField(max_length=300)
+
+ def __str__(self):
+ return self.name
class Booking(models.Model):
@@ -26,6 +41,8 @@ class Booking(models.Model):
jira_issue_id = models.IntegerField(null=True)
jira_issue_status = models.CharField(max_length=50)
+ installer = models.ForeignKey(Installer, models.DO_NOTHING, null=True)
+ scenario = models.ForeignKey(Scenario, models.DO_NOTHING, null=True)
purpose = models.CharField(max_length=300, blank=False)
class Meta:
@@ -40,27 +57,12 @@ class Booking(models.Model):
except JIRAError:
return None
- def authorization_test(self):
- """
- Return True if self.user is authorized to make this booking.
- """
- user = self.user
- # Check if User is troubleshooter / admin
- if user.has_perm('booking.add_booking'):
- return True
- # Check if User owns this resource
- if user == self.resource.owner:
- return True
- return False
-
def save(self, *args, **kwargs):
"""
Save the booking if self.user is authorized and there is no overlapping booking.
Raise PermissionError if the user is not authorized
Raise ValueError if there is an overlapping booking
"""
- if not self.authorization_test():
- raise PermissionError('Insufficient permissions to save this booking.')
if self.start >= self.end:
raise ValueError('Start date is after end date')
# conflicts end after booking starts, and start before booking ends
diff --git a/pharos-dashboard/src/booking/tests/test_models.py b/pharos-dashboard/src/booking/tests/test_models.py
index 612b35c..b4cd113 100644
--- a/pharos-dashboard/src/booking/tests/test_models.py
+++ b/pharos-dashboard/src/booking/tests/test_models.py
@@ -10,12 +10,11 @@
from datetime import timedelta
-from django.contrib.auth.models import User, Permission
+from django.contrib.auth.models import Permission
from django.test import TestCase
from django.utils import timezone
-from account.models import UserProfile
-from booking.models import Booking
+from booking.models import *
from dashboard.models import Resource
from jenkins.models import JenkinsSlave
@@ -37,7 +36,10 @@ class BookingModelTestCase(TestCase):
self.user1 = User.objects.get(pk=self.user1.id)
- def test_start__end(self):
+ self.installer = Installer.objects.create(name='TestInstaller')
+ self.scenario = Scenario.objects.create(name='TestScenario')
+
+ def test_start_end(self):
"""
if the start of a booking is greater or equal then the end, saving should raise a
ValueException
@@ -88,20 +90,5 @@ class BookingModelTestCase(TestCase):
user=self.user1, resource=self.res1))
self.assertTrue(
Booking.objects.create(start=start, end=end,
- user=self.user1, resource=self.res2))
-
- def test_authorization(self):
- user = User.objects.create(username='user')
- user.userprofile = UserProfile.objects.create(user=user)
- self.assertRaises(PermissionError, Booking.objects.create, start=timezone.now(),
- end=timezone.now() + timedelta(days=1), resource=self.res1, user=user)
- self.res1.owner = user
- self.assertTrue(
- Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1),
- resource=self.res1, user=user))
- self.res1.owner = self.owner
- user.user_permissions.add(self.add_booking_perm)
- user = User.objects.get(pk=user.id)
- self.assertTrue(
- Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1),
- resource=self.res2, user=user))
+ user=self.user1, resource=self.res2, scenario=self.scenario,
+ installer=self.installer)) \ No newline at end of file
diff --git a/pharos-dashboard/src/booking/tests/test_views.py b/pharos-dashboard/src/booking/tests/test_views.py
index e568c15..c1da013 100644
--- a/pharos-dashboard/src/booking/tests/test_views.py
+++ b/pharos-dashboard/src/booking/tests/test_views.py
@@ -10,12 +10,10 @@
from datetime import timedelta
-from django.contrib import auth
from django.test import Client
-from django.utils import timezone
-from django.contrib.auth.models import Permission
from django.test import TestCase
from django.urls import reverse
+from django.utils import timezone
from django.utils.encoding import force_text
from registration.forms import User
@@ -37,9 +35,6 @@ class BookingViewTestCase(TestCase):
self.user1profile = UserProfile.objects.create(user=self.user1)
self.user1.save()
- self.add_booking_perm = Permission.objects.get(codename='add_booking')
- self.user1.user_permissions.add(self.add_booking_perm)
-
self.user1 = User.objects.get(pk=self.user1.id)
@@ -78,4 +73,34 @@ class BookingViewTestCase(TestCase):
self.assertIn('resource', response.context)
+ def test_booking_view(self):
+ start = timezone.now()
+ end = start + timedelta(weeks=1)
+ booking = Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1)
+ url = reverse('booking:detail', kwargs={'booking_id':0})
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 404)
+
+ url = reverse('booking:detail', kwargs={'booking_id':booking.id})
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed('booking/booking_detail.html')
+ self.assertIn('booking', response.context)
+
+ def test_booking_list_view(self):
+ start = timezone.now() - timedelta(weeks=2)
+ end = start + timedelta(weeks=1)
+ Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1)
+
+ url = reverse('booking:list')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed('booking/booking_list.html')
+ self.assertTrue(len(response.context['bookings']) == 0)
+
+ start = timezone.now()
+ end = start + timedelta(weeks=1)
+ Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1)
+ response = self.client.get(url)
+ self.assertTrue(len(response.context['bookings']) == 1) \ No newline at end of file
diff --git a/pharos-dashboard/src/booking/urls.py b/pharos-dashboard/src/booking/urls.py
index 5320623..9e01316 100644
--- a/pharos-dashboard/src/booking/urls.py
+++ b/pharos-dashboard/src/booking/urls.py
@@ -34,4 +34,6 @@ urlpatterns = [
url(r'^detail/$', BookingView.as_view(), name='detail_prefix'),
url(r'^detail/(?P<booking_id>[0-9]+)/$', BookingView.as_view(), name='detail'),
+
+ url(r'^list/$', BookingListView.as_view(), name='list')
]
diff --git a/pharos-dashboard/src/booking/views.py b/pharos-dashboard/src/booking/views.py
index 2fe167a..6fdca0e 100644
--- a/pharos-dashboard/src/booking/views.py
+++ b/pharos-dashboard/src/booking/views.py
@@ -8,14 +8,11 @@
##############################################################################
-from datetime import timedelta
-
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
from django.http import JsonResponse
from django.shortcuts import get_object_or_404
-from django.shortcuts import redirect
from django.urls import reverse
from django.utils import timezone
from django.views import View
@@ -46,7 +43,7 @@ def create_jira_ticket(user, booking):
booking.save()
-class BookingFormView(LoginRequiredMixin, FormView):
+class BookingFormView(FormView):
template_name = "booking/booking_calendar.html"
form_class = BookingForm
@@ -64,22 +61,23 @@ class BookingFormView(LoginRequiredMixin, FormView):
return reverse('booking:create', kwargs=self.kwargs)
def form_valid(self, form):
+ if not self.request.user.is_authenticated:
+ messages.add_message(self.request, messages.ERROR,
+ 'You need to be logged in to book a Pod.')
+ return super(BookingFormView, self).form_invalid(form)
+
user = self.request.user
- if not user.userprofile.ssh_public_key or not user.userprofile.pgp_public_key:
- messages.add_message(self.request, messages.INFO,
- 'Please upload your private keys before booking')
- return redirect('account:settings')
- booking = Booking(start=form.cleaned_data['start'], end=form.cleaned_data['end'],
- purpose=form.cleaned_data['purpose'], resource=self.resource,
- user=user)
+ booking = Booking(start=form.cleaned_data['start'],
+ end=form.cleaned_data['end'],
+ purpose=form.cleaned_data['purpose'],
+ installer=form.cleaned_data['installer'],
+ scenario=form.cleaned_data['scenario'],
+ resource=self.resource, user=user)
try:
booking.save()
except ValueError as err:
messages.add_message(self.request, messages.ERROR, err)
return super(BookingFormView, self).form_invalid(form)
- except PermissionError as err:
- messages.add_message(self.request, messages.ERROR, err)
- return super(BookingFormView, self).form_invalid(form)
try:
if settings.CREATE_JIRA_TICKET:
create_jira_ticket(user, booking)
@@ -98,10 +96,20 @@ class BookingView(TemplateView):
def get_context_data(self, **kwargs):
booking = get_object_or_404(Booking, id=self.kwargs['booking_id'])
- jira_issue = booking.get_jira_issue()
title = 'Booking Details'
context = super(BookingView, self).get_context_data(**kwargs)
- context.update({'title': title, 'booking': booking, 'jira_issue': jira_issue})
+ context.update({'title': title, 'booking': booking})
+ return context
+
+
+class BookingListView(TemplateView):
+ template_name = "booking/booking_list.html"
+
+ def get_context_data(self, **kwargs):
+ bookings = Booking.objects.filter(end__gte=timezone.now())
+ title = 'Search Booking'
+ context = super(BookingListView, self).get_context_data(**kwargs)
+ context.update({'title': title, 'bookings': bookings})
return context
@@ -109,5 +117,6 @@ class ResourceBookingsJSON(View):
def get(self, request, *args, **kwargs):
resource = get_object_or_404(Resource, id=self.kwargs['resource_id'])
bookings = resource.booking_set.get_queryset().values('id', 'start', 'end', 'purpose',
- 'jira_issue_status')
- return JsonResponse({'bookings': list(bookings)}) \ No newline at end of file
+ 'jira_issue_status',
+ 'installer__name', 'scenario__name')
+ return JsonResponse({'bookings': list(bookings)})
diff --git a/pharos-dashboard/src/dashboard/migrations/0002_auto_20161005_1202.py b/pharos-dashboard/src/dashboard/migrations/0002_auto_20161005_1202.py
new file mode 100644
index 0000000..e47c3b1
--- /dev/null
+++ b/pharos-dashboard/src/dashboard/migrations/0002_auto_20161005_1202.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-10-05 12:02
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('dashboard', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='resource',
+ name='vpn_users',
+ field=models.ManyToManyField(blank=True, related_name='user_vpn_users', to=settings.AUTH_USER_MODEL),
+ ),
+ ]
diff --git a/pharos-dashboard/src/dashboard/models.py b/pharos-dashboard/src/dashboard/models.py
index 050834e..ec6fec7 100644
--- a/pharos-dashboard/src/dashboard/models.py
+++ b/pharos-dashboard/src/dashboard/models.py
@@ -10,9 +10,9 @@
from datetime import timedelta
-from django.utils import timezone
from django.contrib.auth.models import User
from django.db import models
+from django.utils import timezone
from jenkins.models import JenkinsSlave
@@ -23,7 +23,7 @@ class Resource(models.Model):
description = models.CharField(max_length=300, blank=True, null=True)
url = models.CharField(max_length=100, blank=True, null=True)
owner = models.ForeignKey(User, related_name='user_lab_owner', null=True)
- vpn_users = models.ManyToManyField(User, related_name='user_vpn_users')
+ vpn_users = models.ManyToManyField(User, related_name='user_vpn_users', blank=True)
slave = models.ForeignKey(JenkinsSlave, on_delete=models.DO_NOTHING, null=True)
def get_booking_utilization(self, weeks):
diff --git a/pharos-dashboard/src/dashboard/tasks.py b/pharos-dashboard/src/dashboard/tasks.py
index 4c09bf9..c5ef505 100644
--- a/pharos-dashboard/src/dashboard/tasks.py
+++ b/pharos-dashboard/src/dashboard/tasks.py
@@ -8,8 +8,9 @@
##############################################################################
-from celery import shared_task
from datetime import timedelta
+
+from celery import shared_task
from django.utils import timezone
from jenkins.models import JenkinsStatistic
diff --git a/pharos-dashboard/src/dashboard/templatetags/jira_filters.py b/pharos-dashboard/src/dashboard/templatetags/jira_filters.py
index 7020843..9a97c1d 100644
--- a/pharos-dashboard/src/dashboard/templatetags/jira_filters.py
+++ b/pharos-dashboard/src/dashboard/templatetags/jira_filters.py
@@ -8,9 +8,8 @@
##############################################################################
-from django.template.defaultfilters import register
-
from django.conf import settings
+from django.template.defaultfilters import register
@register.filter
diff --git a/pharos-dashboard/src/dashboard/tests/__init__.py b/pharos-dashboard/src/dashboard/tests/__init__.py
new file mode 100644
index 0000000..b5914ce
--- /dev/null
+++ b/pharos-dashboard/src/dashboard/tests/__init__.py
@@ -0,0 +1,10 @@
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
diff --git a/pharos-dashboard/src/dashboard/tests/test_models.py b/pharos-dashboard/src/dashboard/tests/test_models.py
new file mode 100644
index 0000000..3a3aeab
--- /dev/null
+++ b/pharos-dashboard/src/dashboard/tests/test_models.py
@@ -0,0 +1,69 @@
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from datetime import timedelta
+from math import ceil, floor
+
+from django.test import TestCase
+from django.utils import timezone
+
+from booking.models import *
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+
+
+class ResourceModelTestCase(TestCase):
+ def setUp(self):
+ self.slave = JenkinsSlave.objects.create(name='test', url='test')
+ self.owner = User.objects.create(username='owner')
+
+ self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x',
+ url='x', owner=self.owner)
+
+ def test_booking_utilization(self):
+ utilization = self.res1.get_booking_utilization(1)
+ self.assertTrue(utilization['booked_seconds'] == 0)
+ self.assertTrue(utilization['available_seconds'] == timedelta(weeks=1).total_seconds())
+
+ start = timezone.now() + timedelta(days=1)
+ end = start + timedelta(days=1)
+ booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1,
+ user=self.owner)
+
+ utilization = self.res1.get_booking_utilization(1)
+ booked_seconds = timedelta(days=1).total_seconds()
+ self.assertEqual(utilization['booked_seconds'], booked_seconds)
+
+ utilization = self.res1.get_booking_utilization(-1)
+ self.assertEqual(utilization['booked_seconds'], 0)
+
+ booking.delete()
+ start = timezone.now() - timedelta(days=1)
+ end = start + timedelta(days=2)
+ booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1,
+ user=self.owner)
+ booked_seconds = self.res1.get_booking_utilization(1)['booked_seconds']
+ # use ceil because a fraction of the booked time has already passed now
+ booked_seconds = ceil(booked_seconds)
+ self.assertEqual(booked_seconds, timedelta(days=1).total_seconds())
+
+ booking.delete()
+ start = timezone.now() + timedelta(days=6)
+ end = start + timedelta(days=2)
+ booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1,
+ user=self.owner)
+ booked_seconds = self.res1.get_booking_utilization(1)['booked_seconds']
+ booked_seconds = floor(booked_seconds)
+ self.assertEqual(booked_seconds, timedelta(days=1).total_seconds())
+
+
+
+
+
diff --git a/pharos-dashboard/src/dashboard/tests/test_views.py b/pharos-dashboard/src/dashboard/tests/test_views.py
new file mode 100644
index 0000000..f5e17c2
--- /dev/null
+++ b/pharos-dashboard/src/dashboard/tests/test_views.py
@@ -0,0 +1,75 @@
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from django.test import TestCase
+from django.urls import reverse
+
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+
+
+class DashboardViewTestCase(TestCase):
+ def setUp(self):
+ self.slave_active = JenkinsSlave.objects.create(name='slave_active', url='x', active=True)
+ self.slave_inactive = JenkinsSlave.objects.create(name='slave_inactive', url='x',
+ active=False)
+ self.res_active = Resource.objects.create(name='res_active', slave=self.slave_active,
+ description='x', url='x')
+ self.res_inactive = Resource.objects.create(name='res_inactive', slave=self.slave_inactive,
+ description='x', url='x')
+
+ def test_booking_utilization_json(self):
+ url = reverse('dashboard:booking_utilization', kwargs={'resource_id': 0, 'weeks': 0})
+ self.assertEqual(self.client.get(url).status_code, 404)
+
+ url = reverse('dashboard:booking_utilization', kwargs={'resource_id': self.res_active.id,
+ 'weeks': 0})
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'data')
+
+ def test_jenkins_utilization_json(self):
+ url = reverse('dashboard:jenkins_utilization', kwargs={'resource_id': 0, 'weeks': 0})
+ self.assertEqual(self.client.get(url).status_code, 404)
+
+ url = reverse('dashboard:jenkins_utilization', kwargs={'resource_id': self.res_active.id,
+ 'weeks': 0})
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'data')
+
+ def test_jenkins_slaves_view(self):
+ url = reverse('dashboard:jenkins_slaves')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertIn(self.slave_active, response.context['slaves'])
+ self.assertNotIn(self.slave_inactive, response.context['slaves'])
+
+ def test_ci_pods_view(self):
+ url = reverse('dashboard:ci_pods')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.context['ci_pods']), 0)
+
+ self.slave_active.ci_slave = True
+ self.slave_inactive.ci_slave = True
+ self.slave_active.save()
+ self.slave_inactive.save()
+
+ response = self.client.get(url)
+ self.assertIn(self.res_active, response.context['ci_pods'])
+ self.assertNotIn(self.res_inactive, response.context['ci_pods'])
+
+ def test_dev_pods_view(self):
+ url = reverse('dashboard:dev_pods')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.context['dev_pods']), 0)
+
diff --git a/pharos-dashboard/src/dashboard/urls.py b/pharos-dashboard/src/dashboard/urls.py
index f04f5ca..609e5d6 100644
--- a/pharos-dashboard/src/dashboard/urls.py
+++ b/pharos-dashboard/src/dashboard/urls.py
@@ -24,6 +24,7 @@ Including another URLconf
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
+
from dashboard.views import *
urlpatterns = [
diff --git a/pharos-dashboard/src/dashboard/views.py b/pharos-dashboard/src/dashboard/views.py
index 022a4af..1848844 100644
--- a/pharos-dashboard/src/dashboard/views.py
+++ b/pharos-dashboard/src/dashboard/views.py
@@ -25,7 +25,7 @@ class JenkinsSlavesView(TemplateView):
template_name = "dashboard/jenkins_slaves.html"
def get_context_data(self, **kwargs):
- slaves = JenkinsSlave.objects.all()
+ slaves = JenkinsSlave.objects.filter(active=True)
context = super(JenkinsSlavesView, self).get_context_data(**kwargs)
context.update({'title': "Jenkins Slaves", 'slaves': slaves})
return context
@@ -35,7 +35,7 @@ class CIPodsView(TemplateView):
template_name = "dashboard/ci_pods.html"
def get_context_data(self, **kwargs):
- ci_pods = Resource.objects.filter(slave__ci_slave=True)
+ ci_pods = Resource.objects.filter(slave__ci_slave=True, slave__active=True)
context = super(CIPodsView, self).get_context_data(**kwargs)
context.update({'title': "CI Pods", 'ci_pods': ci_pods})
return context
@@ -45,7 +45,7 @@ class DevelopmentPodsView(TemplateView):
template_name = "dashboard/dev_pods.html"
def get_context_data(self, **kwargs):
- resources = Resource.objects.filter(slave__dev_pod=True)
+ resources = Resource.objects.filter(slave__dev_pod=True, slave__active=True)
bookings = Booking.objects.filter(start__lte=timezone.now())
bookings = bookings.filter(end__gt=timezone.now())
@@ -55,7 +55,7 @@ class DevelopmentPodsView(TemplateView):
booking_utilization = resource.get_booking_utilization(weeks=4)
total = booking_utilization['booked_seconds'] + booking_utilization['available_seconds']
try:
- utilization_percentage = "%d%%" % (float(booking_utilization['booked_seconds']) /
+ utilization_percentage = "%d%%" % (float(booking_utilization['booked_seconds']) /
total * 100)
except (ValueError, ZeroDivisionError):
return ""
@@ -88,7 +88,7 @@ class LabOwnerView(TemplateView):
template_name = "dashboard/resource_all.html"
def get_context_data(self, **kwargs):
- resources = Resource.objects.filter(slave__dev_pod=True)
+ resources = Resource.objects.filter(slave__dev_pod=True, slave__active=True)
pods = []
for resource in resources:
utilization = resource.slave.get_utilization(timedelta(days=7))
diff --git a/pharos-dashboard/src/jenkins/adapter.py b/pharos-dashboard/src/jenkins/adapter.py
index ff0508d..edf502f 100644
--- a/pharos-dashboard/src/jenkins/adapter.py
+++ b/pharos-dashboard/src/jenkins/adapter.py
@@ -9,8 +9,8 @@
import logging
-
import re
+
import requests
from django.core.cache import cache
diff --git a/pharos-dashboard/src/jenkins/models.py b/pharos-dashboard/src/jenkins/models.py
index 0875bba..8254ff3 100644
--- a/pharos-dashboard/src/jenkins/models.py
+++ b/pharos-dashboard/src/jenkins/models.py
@@ -29,6 +29,8 @@ class JenkinsSlave(models.Model):
last_job_installer = models.CharField(max_length=50, default='')
last_job_result = models.CharField(max_length=30, default='')
+ active = models.BooleanField(default=False)
+
def get_utilization(self, timedelta):
"""
Return a dictionary containing the count of idle, online and offline measurements in the time from
diff --git a/pharos-dashboard/src/jenkins/tasks.py b/pharos-dashboard/src/jenkins/tasks.py
index 7c03782..ea986c1 100644
--- a/pharos-dashboard/src/jenkins/tasks.py
+++ b/pharos-dashboard/src/jenkins/tasks.py
@@ -10,6 +10,7 @@
from celery import shared_task
+from dashboard.models import Resource
from jenkins.models import JenkinsSlave, JenkinsStatistic
from .adapter import *
@@ -20,14 +21,28 @@ def sync_jenkins():
def update_jenkins_slaves():
+ JenkinsSlave.objects.all().update(active=False)
+
jenkins_slaves = get_all_slaves()
for slave in jenkins_slaves:
jenkins_slave, created = JenkinsSlave.objects.get_or_create(name=slave['displayName'],
url=get_slave_url(slave))
+ jenkins_slave.active = True
jenkins_slave.ci_slave = is_ci_slave(slave['displayName'])
jenkins_slave.dev_pod = is_dev_pod(slave['displayName'])
jenkins_slave.status = get_slave_status(slave)
+ # if this is a new slave and a pod, check if there is a resource for it, create one if not
+ if created and 'pod' in slave['displayName']:
+ # parse resource name from slave name
+ # naming example: orange-pod1, resource name: Orange POD 1
+ tokens = slave['displayName'].split('-')
+ name = tokens[0].capitalize() + ' POD '# company name
+ name += tokens[1][3:] # remove 'pod'
+ resource, created = Resource.objects.get_or_create(name=name)
+ resource.slave = jenkins_slave
+ resource.save()
+
last_job = get_jenkins_job(jenkins_slave.name)
if last_job is not None:
last_job = parse_job(last_job)
diff --git a/pharos-dashboard/src/jenkins/tests.py b/pharos-dashboard/src/jenkins/tests.py
index 4f350d2..3723cd3 100644
--- a/pharos-dashboard/src/jenkins/tests.py
+++ b/pharos-dashboard/src/jenkins/tests.py
@@ -8,9 +8,11 @@
##############################################################################
+from datetime import timedelta
from unittest import TestCase
import jenkins.adapter as jenkins
+from jenkins.models import *
# Tests that the data we get with the jenkinsadapter contains all the
@@ -28,12 +30,27 @@ class JenkinsAdapterTestCase(TestCase):
self.assertTrue('idle' in slave)
self.assertTrue('offline' in slave)
+ def test_get_slave(self):
+ slaves = jenkins.get_all_slaves()
+ self.assertEqual(slaves[0], jenkins.get_slave(slaves[0]['displayName']))
+ self.assertEqual({}, jenkins.get_slave('098f6bcd4621d373cade4e832627b4f6'))
+
def test_get_ci_slaves(self):
slaves = jenkins.get_ci_slaves()
self.assertTrue(len(slaves) > 0)
for slave in slaves:
self.assertTrue('nodeName' in slave)
+ def test_get_jenkins_job(self):
+ slaves = jenkins.get_ci_slaves()
+ job = None
+ for slave in slaves:
+ job = jenkins.get_jenkins_job(slave['nodeName'])
+ if job is not None:
+ break
+ # We need to test at least one job
+ self.assertNotEqual(job, None)
+
def test_get_all_jobs(self):
jobs = jenkins.get_all_jobs()
lastBuild = False
@@ -50,3 +67,63 @@ class JenkinsAdapterTestCase(TestCase):
self.assertTrue('timestamp' in job['lastBuild'])
self.assertTrue('builtOn' in job['lastBuild'])
self.assertTrue(lastBuild)
+
+ def test_parse_job(self):
+ job = {
+ "displayName": "apex-deploy-baremetal-os-nosdn-fdio-noha-colorado",
+ "url": "https://build.opnfv.org/ci/job/apex-deploy-baremetal-os-nosdn-fdio-noha-colorado/",
+ "lastBuild": {
+ "building": False,
+ "fullDisplayName": "apex-deploy-baremetal-os-nosdn-fdio-noha-colorado #37",
+ "result": "SUCCESS",
+ "timestamp": 1476283629917,
+ "builtOn": "lf-pod1"
+ }
+ }
+
+ job = jenkins.parse_job(job)
+ self.assertEqual(job['scenario'], 'os-nosdn-fdio-noha')
+ self.assertEqual(job['installer'], 'apex')
+ self.assertEqual(job['branch'], 'colorado')
+ self.assertEqual(job['result'], 'SUCCESS')
+ self.assertEqual(job['building'], False)
+ self.assertEqual(job['url'],
+ "https://build.opnfv.org/ci/job/apex-deploy-baremetal-os-nosdn-fdio-noha-colorado/")
+ self.assertEqual(job['name'],
+ 'apex-deploy-baremetal-os-nosdn-fdio-noha-colorado')
+
+ def test_get_slave_status(self):
+ slave = {
+ 'offline': True,
+ 'idle': False
+ }
+ self.assertEqual(jenkins.get_slave_status(slave), 'offline')
+ slave = {
+ 'offline': False,
+ 'idle': False
+ }
+ self.assertEqual(jenkins.get_slave_status(slave), 'online')
+ slave = {
+ 'offline': False,
+ 'idle': True
+ }
+ self.assertEqual(jenkins.get_slave_status(slave), 'online / idle')
+
+
+class JenkinsModelTestCase(TestCase):
+ def test_get_utilization(self):
+ jenkins_slave = JenkinsSlave.objects.create(name='test', status='offline', url='')
+ utilization = jenkins_slave.get_utilization(timedelta(weeks=1))
+ self.assertEqual(utilization['idle'], 0)
+ self.assertEqual(utilization['offline'], 0)
+ self.assertEqual(utilization['online'], 0)
+
+ for i in range(10):
+ JenkinsStatistic.objects.create(slave=jenkins_slave,
+ offline=True, idle=True,
+ online=True)
+
+ utilization = jenkins_slave.get_utilization(timedelta(weeks=1))
+ self.assertEqual(utilization['idle'], 10)
+ self.assertEqual(utilization['offline'], 10)
+ self.assertEqual(utilization['online'], 10)
diff --git a/pharos-dashboard/src/notification/models.py b/pharos-dashboard/src/notification/models.py
index 2d19918..0ee275d 100644
--- a/pharos-dashboard/src/notification/models.py
+++ b/pharos-dashboard/src/notification/models.py
@@ -26,7 +26,8 @@ class BookingNotification(models.Model):
}
def save(self, *args, **kwargs):
- notifications = self.booking.bookingnotification_set.filter(type=self.type)
- if notifications.count() > 1:
+ notifications = self.booking.bookingnotification_set.filter(type=self.type).exclude(
+ id=self.id)
+ if notifications.count() > 0:
raise ValueError('Doubled Notification')
return super(BookingNotification, self).save(*args, **kwargs) \ No newline at end of file
diff --git a/pharos-dashboard/src/notification/tasks.py b/pharos-dashboard/src/notification/tasks.py
index 61ab14a..4173433 100644
--- a/pharos-dashboard/src/notification/tasks.py
+++ b/pharos-dashboard/src/notification/tasks.py
@@ -8,9 +8,9 @@
##############################################################################
-from celery import shared_task
from datetime import timedelta
+from celery import shared_task
from django.conf import settings
from django.utils import timezone
diff --git a/pharos-dashboard/src/notification/tests.py b/pharos-dashboard/src/notification/tests.py
new file mode 100644
index 0000000..9df9aa6
--- /dev/null
+++ b/pharos-dashboard/src/notification/tests.py
@@ -0,0 +1,41 @@
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from datetime import timedelta
+from unittest import TestCase
+
+from django.contrib.auth.models import User
+from django.utils import timezone
+
+from booking.models import Booking
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+from notification.models import *
+
+
+class JenkinsModelTestCase(TestCase):
+ def setUp(self):
+ self.slave = JenkinsSlave.objects.create(name='test1', url='test')
+ self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x',
+ url='x')
+ self.user1 = User.objects.create(username='user1')
+
+ start = timezone.now()
+ end = start + timedelta(days=1)
+ self.booking = Booking.objects.create(start=start, end=end, purpose='test',
+ resource=self.res1, user=self.user1)
+
+ def test_booking_notification(self):
+ BookingNotification.objects.create(type='test', booking=self.booking,
+ submit_time=timezone.now())
+
+ self.assertRaises(ValueError, BookingNotification.objects.create, type='test',
+ booking=self.booking,
+ submit_time=timezone.now())
diff --git a/pharos-dashboard/src/static/js/booking-calendar.js b/pharos-dashboard/src/static/js/booking-calendar.js
index 9cb0f32..f634293 100644
--- a/pharos-dashboard/src/static/js/booking-calendar.js
+++ b/pharos-dashboard/src/static/js/booking-calendar.js
@@ -1,11 +1,11 @@
/*****************************************************************************
-* Copyright (c) 2016 Max Breitenfeldt and others.
-*
-* All rights reserved. This program and the accompanying materials
-* are made available under the terms of the Apache License, Version 2.0
-* which accompanies this distribution, and is available at
-* http://www.apache.org/licenses/LICENSE-2.0
-*****************************************************************************/
+ * Copyright (c) 2016 Max Breitenfeldt and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Apache License, Version 2.0
+ * which accompanies this distribution, and is available at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *****************************************************************************/
function parseCalendarEvents(bookings) {
@@ -14,9 +14,21 @@ function parseCalendarEvents(bookings) {
// convert ISO 8601 timestring to moment, needed for timezone handling
start = moment(bookings[i]['start']);
end = moment(bookings[i]['end']);
+
+ installer = bookings[i]['installer__name'];
+ if (installer === null) {
+ installer = '';
+ }
+
+ scenario = bookings[i]['scenario__name'];
+ if (scenario === null) {
+ scenario = '';
+ }
+ title = bookings[i]['purpose'] + ' ' + installer + ' ' + scenario;
+
event = {
id: bookings[i]['id'],
- title: bookings[i]['purpose'],
+ title: title,
start: start,
end: end,
};
diff --git a/pharos-dashboard/src/templates/account/user_list.html b/pharos-dashboard/src/templates/account/user_list.html
index c2b8193..f18e161 100644
--- a/pharos-dashboard/src/templates/account/user_list.html
+++ b/pharos-dashboard/src/templates/account/user_list.html
@@ -5,6 +5,8 @@
<thead>
<tr>
<th>Username</th>
+ <th>Full Name</th>
+ <th>Email</th>
<th>Company</th>
<th>SSH Key</th>
<th>GPG Key</th>
@@ -17,17 +19,23 @@
{{ user.username }}
</td>
<td>
+ {{ user.userprofile.full_name }}
+ </td>
+ <td>
+ {{ user.email }}
+ </td>
+ <td>
{{ user.userprofile.company }}
</td>
<td>
- {% if user.userprofile.ssh_public_key %}
- <a href={{ user.userprofile.ssh_public_key.url }}>SSH</a>
- {% endif %}
+ {% if user.userprofile.ssh_public_key %}
+ <a href={{ user.userprofile.ssh_public_key.url }}>SSH</a>
+ {% endif %}
</td>
<td>
- {% if user.userprofile.pgp_public_key %}
- <a href={{ user.userprofile.pgp_public_key.url }}>GPG</a>
- {% endif %}
+ {% if user.userprofile.pgp_public_key %}
+ <a href={{ user.userprofile.pgp_public_key.url }}>GPG</a>
+ {% endif %}
</td>
</tr>
{% endfor %}
diff --git a/pharos-dashboard/src/templates/account/userprofile_update_form.html b/pharos-dashboard/src/templates/account/userprofile_update_form.html
index 542ea81..f4bb7b5 100644
--- a/pharos-dashboard/src/templates/account/userprofile_update_form.html
+++ b/pharos-dashboard/src/templates/account/userprofile_update_form.html
@@ -16,9 +16,17 @@
<form enctype="multipart/form-data" method="post">
{% csrf_token %}
{% bootstrap_form form %}
+ <p><b>API Token</b>
+ <a href="{% url 'generate_token' %}" class="btn btn-default">
+ Generate
+ </a>
+ </p>
+ <p style="word-wrap: break-word;">{{ token.key }}</p>
+
+ <p></p>
{% buttons %}
<button type="submit" class="btn btn btn-success">
- Save
+ Save Profile
</button>
{% endbuttons %}
</form>
diff --git a/pharos-dashboard/src/templates/base.html b/pharos-dashboard/src/templates/base.html
index 5bb5547..2ce22a3 100644
--- a/pharos-dashboard/src/templates/base.html
+++ b/pharos-dashboard/src/templates/base.html
@@ -67,8 +67,15 @@
Slaves</a>
</li>
<li>
+ {% if user.is_authenticated %}
<a href="{% url 'account:users' %}"><i
- class="fa fa-fw"></i>Users
+ class="fa fa-fw"></i>User List
+ </a>
+ {% endif %}
+ </li>
+ <li>
+ <a href="{% url 'booking:list' %}"><i
+ class="fa fa-fw"></i>Booking List
</a>
</li>
<li>
diff --git a/pharos-dashboard/src/templates/booking/booking_calendar.html b/pharos-dashboard/src/templates/booking/booking_calendar.html
index de3e3b3..4644e85 100644
--- a/pharos-dashboard/src/templates/booking/booking_calendar.html
+++ b/pharos-dashboard/src/templates/booking/booking_calendar.html
@@ -1,4 +1,4 @@
-{% extends "dashboard/table.html" %}
+{% extends "base.html" %}
{% load staticfiles %}
{% load bootstrap3 %}
@@ -33,26 +33,34 @@
<i class="fa fa-edit fa-fw"></i>Booking
</div>
<div class="panel-body">
- <div id="booking_form_div">
- {% bootstrap_form_errors form type='non_fields' %}
- <form method="post" action="" class="form" id="bookingform">
- {% csrf_token %}
+ {% if user.is_authenticated %}
+ <div id="booking_form_div">
+ {% bootstrap_form_errors form type='non_fields' %}
+ <form method="post" action="" class="form" id="bookingform">
+ {% csrf_token %}
- <div class='input-group' id='starttimepicker'>
- {% bootstrap_field form.start addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
- </div>
- <div class='input-group' id='endtimepicker'>
- {% bootstrap_field form.end addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
- </div>
- {% bootstrap_field form.purpose %}
-
- {% buttons %}
- <button type="submit" class="btn btn btn-success">
- Book
- </button>
- {% endbuttons %}
- </form>
- </div>
+ <div class='input-group' id='starttimepicker'>
+ {% bootstrap_field form.start addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
+ </div>
+ <div class='input-group' id='endtimepicker'>
+ {% bootstrap_field form.end addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
+ </div>
+ {% bootstrap_field form.purpose %}
+ {% bootstrap_field form.installer %}
+ {% bootstrap_field form.scenario %}
+ {% buttons %}
+ <button type="submit" class="btn btn btn-success">
+ Book
+ </button>
+ {% endbuttons %}
+ </form>
+ </div>
+ {% else %}
+ <p>Please
+ <a href="{% url 'account:login' %}">
+ login with Jira</a>
+ to book this Pod</p>
+ {% endif %}
</div>
</div>
</div>
@@ -69,7 +77,8 @@
<div class="modal-body" id="booking_detail_content">
</div>
<div class="modal-footer">
- <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
+ <button type="button" class="btn btn-default" data-dismiss="modal">Close
+ </button>
</div>
</div>
diff --git a/pharos-dashboard/src/templates/booking/booking_detail.html b/pharos-dashboard/src/templates/booking/booking_detail.html
index d3f4753..4b016b2 100644
--- a/pharos-dashboard/src/templates/booking/booking_detail.html
+++ b/pharos-dashboard/src/templates/booking/booking_detail.html
@@ -19,8 +19,8 @@
<b>Purpose: </b> {{ booking.purpose }}
</p>
<p>
- <b>Jira: </b>
- <a href="{{ jira_issue | jira_issue_url }}">
- {{ jira_issue }}
- </a>
+ <b>Installer: </b> {{ booking.installer }}
+</p>
+<p>
+ <b>Scenario: </b> {{ booking.scenario }}
</p> \ No newline at end of file
diff --git a/pharos-dashboard/src/templates/booking/booking_list.html b/pharos-dashboard/src/templates/booking/booking_list.html
new file mode 100644
index 0000000..f2991e4
--- /dev/null
+++ b/pharos-dashboard/src/templates/booking/booking_list.html
@@ -0,0 +1,50 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+
+{% block extrahead %}
+ <!-- DataTables CSS -->
+ <link href="{% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css" %}"
+ rel="stylesheet">
+
+ <!-- DataTables Responsive CSS -->
+ <link href="{% static "bower_components/datatables-responsive/css/dataTables.responsive.css" %}"
+ rel="stylesheet">
+{% endblock extrahead %}
+
+{% block content %}
+ <div class="row">
+ <div class="col-lg-12">
+ <div class="panel panel-default">
+ <div class="panel-body">
+ <div class="dataTables_wrapper">
+ <table class="table table-striped table-bordered table-hover" id="table"
+ cellspacing="0"
+ width="100%">
+ {% include "booking/booking_table.html" %}
+ </table>
+ </div>
+ <!-- /.table-responsive -->
+ </div>
+ <!-- /.panel-body -->
+ </div>
+ <!-- /.panel -->
+ </div>
+ <!-- /.col-lg-12 -->
+ </div>
+{% endblock content %}
+
+{% block extrajs %}
+ <!-- DataTables JavaScript -->
+ <link href="{% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.css" %}"
+ rel="stylesheet">
+
+
+ <script src={% static "bower_components/datatables/media/js/jquery.dataTables.min.js" %}></script>
+ <script src={% static "bower_components/datatables-plugins/integration/bootstrap/3/dataTables.bootstrap.min.js" %}></script>
+
+ <script type="text/javascript">
+ $(document).ready(function () {
+ $('#table').DataTable({});
+ });
+ </script>
+{% endblock extrajs %} \ No newline at end of file
diff --git a/pharos-dashboard/src/templates/booking/booking_table.html b/pharos-dashboard/src/templates/booking/booking_table.html
index 216eaf5..655b013 100644
--- a/pharos-dashboard/src/templates/booking/booking_table.html
+++ b/pharos-dashboard/src/templates/booking/booking_table.html
@@ -7,7 +7,8 @@
<th>Purpose</th>
<th>Start</th>
<th>End</th>
- <th>Jira</th>
+ <th>Installer</th>
+ <th>Scenario</th>
</tr>
</thead>
<tbody>
@@ -25,8 +26,11 @@
<td>
{{ booking.end }}
</td>
- <td><a target='_blank'
- href={{ booking.get_jira_issue | jira_issue_url }}>{{ booking.get_jira_issue }}</a>
+ <td>
+ {{ booking.installer }}
+ </td>
+ <td>
+ {{ booking.scenario }}
</td>
</tr>
{% endfor %}
diff --git a/pharos-dashboard/src/templates/dashboard/resource_detail.html b/pharos-dashboard/src/templates/dashboard/resource_detail.html
index 657d565..e0b29bd 100644
--- a/pharos-dashboard/src/templates/dashboard/resource_detail.html
+++ b/pharos-dashboard/src/templates/dashboard/resource_detail.html
@@ -106,6 +106,7 @@
</p>
<p>
<b>Email: </b>
+ {{ resource.owner.email }}
</p>
<p>
<a href="{% url 'booking:create' resource_id=resource.id %}" class="btn