summaryrefslogtreecommitdiffstats
path: root/dashboard/src/booking
diff options
context:
space:
mode:
authorParker Berberian <pberberian@iol.unh.edu>2018-10-10 16:06:47 -0400
committerParker Berberian <pberberian@iol.unh.edu>2018-10-15 13:16:11 -0400
commit25275685e9a735e51fae8b1a936ba5733f6fb770 (patch)
treec3af41d1986c0812ca7ed059c16c7c7c7bd354cf /dashboard/src/booking
parentf2e83b24260b018bb7cc30421eda6c9a8cebc309 (diff)
Lab as a Service 2.0
See changes here: https://wiki.opnfv.org/display/INF/Pharos+Laas Change-Id: I59ada5f98e70a28d7f8c14eab3239597e236ca26 Signed-off-by: Sawyer Bergeron <sbergeron@iol.unh.edu> Signed-off-by: Parker Berberian <pberberian@iol.unh.edu>
Diffstat (limited to 'dashboard/src/booking')
-rw-r--r--dashboard/src/booking/__init__.py2
-rw-r--r--dashboard/src/booking/admin.py4
-rw-r--r--dashboard/src/booking/forms.py65
-rw-r--r--dashboard/src/booking/migrations/0001_initial.py48
-rw-r--r--dashboard/src/booking/migrations/0002_booking_changeid.py38
-rw-r--r--dashboard/src/booking/migrations/0003_auto_20180108_2024.py25
-rw-r--r--dashboard/src/booking/migrations/0004_booking_ext_count.py27
-rw-r--r--dashboard/src/booking/migrations/__init__.py10
-rw-r--r--dashboard/src/booking/models.py60
-rw-r--r--dashboard/src/booking/stats.py56
-rw-r--r--dashboard/src/booking/tests/__init__.py2
-rw-r--r--dashboard/src/booking/tests/test_models.py216
-rw-r--r--dashboard/src/booking/tests/test_views.py106
-rw-r--r--dashboard/src/booking/urls.py16
-rw-r--r--dashboard/src/booking/views.py138
15 files changed, 376 insertions, 437 deletions
diff --git a/dashboard/src/booking/__init__.py b/dashboard/src/booking/__init__.py
index b5914ce..b6fef6c 100644
--- a/dashboard/src/booking/__init__.py
+++ b/dashboard/src/booking/__init__.py
@@ -6,5 +6,3 @@
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-
-
diff --git a/dashboard/src/booking/admin.py b/dashboard/src/booking/admin.py
index 51e1031..2beb05b 100644
--- a/dashboard/src/booking/admin.py
+++ b/dashboard/src/booking/admin.py
@@ -1,5 +1,6 @@
##############################################################################
# Copyright (c) 2016 Max Breitenfeldt and others.
+# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
@@ -13,6 +14,3 @@ from django.contrib import admin
from booking.models import *
admin.site.register(Booking)
-admin.site.register(Opsys)
-admin.site.register(Installer)
-admin.site.register(Scenario)
diff --git a/dashboard/src/booking/forms.py b/dashboard/src/booking/forms.py
deleted file mode 100644
index 9d71b42..0000000
--- a/dashboard/src/booking/forms.py
+++ /dev/null
@@ -1,65 +0,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
-##############################################################################
-
-
-import django.forms as forms
-
-from booking.models import Installer, Scenario, Opsys
-from datetime import datetime
-
-class BookingForm(forms.Form):
- fields = ['start', 'end', 'purpose', 'opsys', 'reset', 'installer', 'scenario']
-
- start = forms.DateTimeField()
- end = forms.DateTimeField()
- reset = forms.ChoiceField(choices = ((True, 'Yes'),(False, 'No')), label="Reset System", initial='False', required=False)
- purpose = forms.CharField(max_length=300)
- opsys = forms.ModelChoiceField(queryset=Opsys.objects.all(), required=False)
- opsys.label = "Operating System"
- installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=False)
- scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=False)
-
-class BookingEditForm(forms.Form):
- fields = ['start', 'end', 'purpose', 'opsys', 'reset', 'installer', 'scenario']
-
- start = forms.DateTimeField()
- end = forms.DateTimeField()
- purpose = forms.CharField(max_length=300)
- opsys = forms.ModelChoiceField(queryset=Opsys.objects.all(), required=False)
- installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=False)
- scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=False)
- reset = forms.ChoiceField(choices = ((True, 'Yes'),(False, 'No')), label="Reset System", initial='False', required=True)
-
-
- def __init__(self, *args, **kwargs ):
- cloned_kwargs = {}
- cloned_kwargs['purpose'] = kwargs.pop('purpose')
- cloned_kwargs['start'] = kwargs.pop('start')
- cloned_kwargs['end'] = kwargs.pop('end')
- if 'installer' in kwargs:
- cloned_kwargs['installer'] = kwargs.pop('installer')
- if 'scenario' in kwargs:
- cloned_kwargs['scenario'] = kwargs.pop('scenario')
- super(BookingEditForm, self).__init__( *args, **kwargs)
-
- self.fields['purpose'].initial = cloned_kwargs['purpose']
- self.fields['start'].initial = cloned_kwargs['start'].strftime('%m/%d/%Y %H:%M')
- self.fields['end'].initial = cloned_kwargs['end'].strftime('%m/%d/%Y %H:%M')
- try:
- self.fields['installer'].initial = cloned_kwargs['installer'].id
- except KeyError:
- pass
- except AttributeError:
- pass
- try:
- self.fields['scenario'].initial = cloned_kwargs['scenario'].id
- except KeyError:
- pass
- except AttributeError:
- pass
diff --git a/dashboard/src/booking/migrations/0001_initial.py b/dashboard/src/booking/migrations/0001_initial.py
index 6932dae..20415fe 100644
--- a/dashboard/src/booking/migrations/0001_initial.py
+++ b/dashboard/src/booking/migrations/0001_initial.py
@@ -1,6 +1,4 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.10 on 2016-11-03 13:33
-from __future__ import unicode_literals
+# Generated by Django 2.1 on 2018-09-14 14:48
from django.conf import settings
from django.db import migrations, models
@@ -12,8 +10,9 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
- ('dashboard', '0001_initial'),
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('account', '0001_initial'),
+ ('resource_inventory', '__first__'),
]
operations = [
@@ -23,9 +22,17 @@ class Migration(migrations.Migration):
('id', models.AutoField(primary_key=True, serialize=False)),
('start', models.DateTimeField()),
('end', models.DateTimeField()),
- ('jira_issue_id', models.IntegerField(null=True)),
- ('jira_issue_status', models.CharField(max_length=50)),
+ ('reset', models.BooleanField(default=False)),
+ ('jira_issue_id', models.IntegerField(blank=True, null=True)),
+ ('jira_issue_status', models.CharField(blank=True, max_length=50)),
('purpose', models.CharField(max_length=300)),
+ ('ext_count', models.IntegerField(default=2)),
+ ('project', models.CharField(blank=True, default='', max_length=100, null=True)),
+ ('collaborators', models.ManyToManyField(related_name='collaborators', to=settings.AUTH_USER_MODEL)),
+ ('config_bundle', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ConfigBundle')),
+ ('lab', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='account.Lab')),
+ ('owner', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='owner', to=settings.AUTH_USER_MODEL)),
+ ('resource', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ResourceBundle')),
],
options={
'db_table': 'booking',
@@ -39,6 +46,14 @@ class Migration(migrations.Migration):
],
),
migrations.CreateModel(
+ name='Opsys',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('name', models.CharField(max_length=100)),
+ ('sup_installers', models.ManyToManyField(blank=True, to='booking.Installer')),
+ ],
+ ),
+ migrations.CreateModel(
name='Scenario',
fields=[
('id', models.AutoField(primary_key=True, serialize=False)),
@@ -46,23 +61,8 @@ class Migration(migrations.Migration):
],
),
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='resource',
- field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dashboard.Resource'),
- ),
- migrations.AddField(
- model_name='booking',
- name='scenario',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='booking.Scenario'),
- ),
- migrations.AddField(
- model_name='booking',
- name='user',
- field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
+ model_name='installer',
+ name='sup_scenarios',
+ field=models.ManyToManyField(blank=True, to='booking.Scenario'),
),
]
diff --git a/dashboard/src/booking/migrations/0002_booking_changeid.py b/dashboard/src/booking/migrations/0002_booking_changeid.py
deleted file mode 100644
index 33af8fd..0000000
--- a/dashboard/src/booking/migrations/0002_booking_changeid.py
+++ /dev/null
@@ -1,38 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.10 on 2017-12-13 15:06
-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='Opsys',
- fields=[
- ('id', models.AutoField(primary_key=True, serialize=False)),
- ('name', models.CharField(max_length=100)),
- ],
- ),
- migrations.AddField(
- model_name='booking',
- name='opsys',
- field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='booking.Opsys'),
- ),
- migrations.AddField(
- model_name='booking',
- name='changeid',
- field=models.TextField(default='no change ID'),
- ),
- migrations.AlterField(
- model_name='booking',
- name='changeid',
- field=models.TextField(blank=True, default='no change ID', null=True),
- ),
- ]
diff --git a/dashboard/src/booking/migrations/0003_auto_20180108_2024.py b/dashboard/src/booking/migrations/0003_auto_20180108_2024.py
deleted file mode 100644
index 93cecc2..0000000
--- a/dashboard/src/booking/migrations/0003_auto_20180108_2024.py
+++ /dev/null
@@ -1,25 +0,0 @@
-# -*- coding: utf-8 -*-
-# Generated by Django 1.10 on 2018-01-08 20:24
-from __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('booking', '0002_booking_changeid'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='booking',
- name='reset',
- field=models.BooleanField(default=False),
- ),
- migrations.AlterField(
- model_name='booking',
- name='changeid',
- field=models.TextField(blank=True, default='initial', null=True),
- ),
- ] \ No newline at end of file
diff --git a/dashboard/src/booking/migrations/0004_booking_ext_count.py b/dashboard/src/booking/migrations/0004_booking_ext_count.py
deleted file mode 100644
index 6bcc3ce..0000000
--- a/dashboard/src/booking/migrations/0004_booking_ext_count.py
+++ /dev/null
@@ -1,27 +0,0 @@
-##############################################################################
-# Copyright (c) 2018 Sawyer Bergeron 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 __future__ import unicode_literals
-
-from django.db import migrations, models
-
-
-class Migration(migrations.Migration):
-
- dependencies = [
- ('booking', '0003_auto_20180108_2024'),
- ]
-
- operations = [
- migrations.AddField(
- model_name='booking',
- name='ext_count',
- field=models.IntegerField(default=2),
- ),
- ]
diff --git a/dashboard/src/booking/migrations/__init__.py b/dashboard/src/booking/migrations/__init__.py
index b5914ce..e69de29 100644
--- a/dashboard/src/booking/migrations/__init__.py
+++ b/dashboard/src/booking/migrations/__init__.py
@@ -1,10 +0,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
-##############################################################################
-
-
diff --git a/dashboard/src/booking/models.py b/dashboard/src/booking/models.py
index 8762d46..adfc28d 100644
--- a/dashboard/src/booking/models.py
+++ b/dashboard/src/booking/models.py
@@ -1,5 +1,6 @@
##############################################################################
# Copyright (c) 2016 Max Breitenfeldt and others.
+# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
@@ -8,67 +9,61 @@
##############################################################################
+from resource_inventory.models import ResourceBundle, ConfigBundle
+from account.models import Lab
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 django.utils.crypto import get_random_string
-import hashlib
+import resource_inventory.resource_manager
-from dashboard.models import Resource
-
-class Installer(models.Model):
+class Scenario(models.Model):
id = models.AutoField(primary_key=True)
- name = models.CharField(max_length=30)
+ name = models.CharField(max_length=300)
def __str__(self):
return self.name
-class Scenario(models.Model):
+
+class Installer(models.Model):
id = models.AutoField(primary_key=True)
- name = models.CharField(max_length=300)
+ name = models.CharField(max_length=30)
+ sup_scenarios = models.ManyToManyField(Scenario, blank=True)
def __str__(self):
return self.name
+
class Opsys(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
+ sup_installers = models.ManyToManyField(Installer, blank=True)
def __str__(self):
return self.name
+
class Booking(models.Model):
id = models.AutoField(primary_key=True)
- changeid = models.TextField(default='initial', blank=True, null=True)
- user = models.ForeignKey(User, models.CASCADE) # delete if user is deleted
- resource = models.ForeignKey(Resource, models.PROTECT)
+ owner = models.ForeignKey(User, models.CASCADE, related_name='owner') # delete if user is deleted
+ collaborators = models.ManyToManyField(User, related_name='collaborators')
start = models.DateTimeField()
end = models.DateTimeField()
reset = models.BooleanField(default=False)
- jira_issue_id = models.IntegerField(null=True)
- jira_issue_status = models.CharField(max_length=50)
-
- opsys = models.ForeignKey(Opsys, models.DO_NOTHING, null=True)
- installer = models.ForeignKey(Installer, models.DO_NOTHING, null=True)
- scenario = models.ForeignKey(Scenario, models.DO_NOTHING, null=True)
+ jira_issue_id = models.IntegerField(null=True, blank=True)
+ jira_issue_status = models.CharField(max_length=50, blank=True)
purpose = models.CharField(max_length=300, blank=False)
ext_count = models.IntegerField(default=2)
+ resource = models.ForeignKey(ResourceBundle, on_delete=models.SET_NULL, null=True) #need to decide behavior here on delete
+ config_bundle = models.ForeignKey(ConfigBundle, on_delete=models.SET_NULL, null=True)
+ project = models.CharField(max_length=100, default="", blank=True, null=True)
+ lab = models.ForeignKey(Lab, null=True, on_delete=models.SET_NULL)
class Meta:
db_table = 'booking'
- def get_jira_issue(self):
- try:
- jira = JIRA(server=settings.JIRA_URL,
- basic_auth=(settings.JIRA_USER_NAME, settings.JIRA_USER_PASSWORD))
- issue = jira.issue(self.jira_issue_id)
- return issue
- except JIRAError:
- return None
-
def save(self, *args, **kwargs):
"""
Save the booking if self.user is authorized and there is no overlapping booking.
@@ -83,11 +78,14 @@ class Booking(models.Model):
conflicting_dates = conflicting_dates.filter(start__lt=self.end)
if conflicting_dates.count() > 0:
raise ValueError('This booking overlaps with another booking')
- if not self.changeid:
- self.changeid = self.id
- else:
- self.changeid = hashlib.md5(self.changeid.encode() + get_random_string(length=32).encode()).hexdigest()
return super(Booking, self).save(*args, **kwargs)
+ def delete(self, *args, **kwargs):
+ res = self.resource
+ self.resource = None
+ self.save()
+ resource_inventory.resource_manager.ResourceManager.getInstance().deleteResourceBundle(res)
+ return super(self.__class__, self).delete(*args, **kwargs)
+
def __str__(self):
- return str(self.resource) + ' from ' + str(self.start) + ' until ' + str(self.end)
+ return str(self.purpose) + ' from ' + str(self.start) + ' until ' + str(self.end)
diff --git a/dashboard/src/booking/stats.py b/dashboard/src/booking/stats.py
new file mode 100644
index 0000000..31f7ef1
--- /dev/null
+++ b/dashboard/src/booking/stats.py
@@ -0,0 +1,56 @@
+##############################################################################
+# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, 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 booking.models import Booking
+import datetime
+import pytz
+
+
+class StatisticsManager(object):
+
+ @staticmethod
+ def getContinuousBookingTimeSeries(span=28):
+ """
+ Will return a dictionary of names and 2-D array of x and y data points.
+ e.g. {"plot1": [["x1", "x2", "x3"],["y1", "y2", "y3]]}
+ x values will be dates in string
+ every change (booking start / end) will be reflected, instead of one data point per day
+ y values are the integer number of bookings/users active at some point in the given date
+ span is the number of days to plot. The last x value will always be the current time
+ """
+ x_set = set()
+ x = []
+ y = []
+ users = []
+ now = datetime.datetime.now(pytz.utc)
+ delta = datetime.timedelta(days=span)
+ end = now-delta
+ bookings = Booking.objects.filter(start__lte=now, end__gte=end)
+ for booking in bookings:
+ x_set.add(booking.start)
+ if booking.end < now:
+ x_set.add(booking.end)
+
+ x_set.add(now)
+ x_set.add(end)
+
+ x_list = list(x_set)
+ x_list.sort(reverse=True)
+ for time in x_list:
+ x.append(str(time))
+ active = Booking.objects.filter(start__lte=time, end__gt=time)
+ booking_count = len(active)
+ users_set = set()
+ for booking in active:
+ users_set.add(booking.owner)
+ for user in booking.collaborators.all():
+ users_set.add(user)
+ y.append(booking_count)
+ users.append(len(users_set))
+
+ return {"booking": [x, y], "user": [x, users]}
diff --git a/dashboard/src/booking/tests/__init__.py b/dashboard/src/booking/tests/__init__.py
index b5914ce..b6fef6c 100644
--- a/dashboard/src/booking/tests/__init__.py
+++ b/dashboard/src/booking/tests/__init__.py
@@ -6,5 +6,3 @@
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-
-
diff --git a/dashboard/src/booking/tests/test_models.py b/dashboard/src/booking/tests/test_models.py
index b4cd113..a83f817 100644
--- a/dashboard/src/booking/tests/test_models.py
+++ b/dashboard/src/booking/tests/test_models.py
@@ -1,5 +1,6 @@
##############################################################################
# Copyright (c) 2016 Max Breitenfeldt and others.
+# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
@@ -15,29 +16,28 @@ from django.test import TestCase
from django.utils import timezone
from booking.models import *
-from dashboard.models import Resource
-from jenkins.models import JenkinsSlave
-
+from resource_inventory.models import ResourceBundle, GenericResourceBundle, ConfigBundle
class BookingModelTestCase(TestCase):
+ count = 0
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)
- self.res2 = Resource.objects.create(name='res2', slave=self.slave, description='x',
- url='x',owner=self.owner)
-
+ self.res1 = ResourceBundle.objects.create(
+ template=GenericResourceBundle.objects.create(name="gbundle" + str(self.count))
+ )
+ self.count += 1
+ self.res2 = ResourceBundle.objects.create(
+ template=GenericResourceBundle.objects.create(name="gbundle2" + str(self.count))
+ )
+ self.count += 1
self.user1 = User.objects.create(username='user1')
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)
-
- self.installer = Installer.objects.create(name='TestInstaller')
- self.scenario = Scenario.objects.create(name='TestScenario')
+ self.config_bundle = ConfigBundle.objects.create(owner=self.user1, name="test config")
def test_start_end(self):
"""
@@ -46,11 +46,25 @@ class BookingModelTestCase(TestCase):
"""
start = timezone.now()
end = start - timedelta(weeks=1)
- self.assertRaises(ValueError, Booking.objects.create, start=start, end=end,
- resource=self.res1, user=self.user1)
+ self.assertRaises(
+ ValueError,
+ Booking.objects.create,
+ start=start,
+ end=end,
+ resource=self.res1,
+ owner=self.user1,
+ config_bundle=self.config_bundle
+ )
end = start
- self.assertRaises(ValueError, Booking.objects.create, start=start, end=end,
- resource=self.res1, user=self.user1)
+ self.assertRaises(
+ ValueError,
+ Booking.objects.create,
+ start=start,
+ end=end,
+ resource=self.res1,
+ owner=self.user1,
+ config_bundle=self.config_bundle
+ )
def test_conflicts(self):
"""
@@ -60,35 +74,153 @@ class BookingModelTestCase(TestCase):
start = timezone.now()
end = start + timedelta(weeks=1)
self.assertTrue(
- Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1))
-
- self.assertRaises(ValueError, Booking.objects.create, start=start,
- end=end, resource=self.res1, user=self.user1)
- self.assertRaises(ValueError, Booking.objects.create, start=start + timedelta(days=1),
- end=end - timedelta(days=1), resource=self.res1, user=self.user1)
+ Booking.objects.create(
+ start=start,
+ end=end,
+ owner=self.user1,
+ resource=self.res1,
+ config_bundle=self.config_bundle
+ )
+ )
+
+ self.assertRaises(
+ ValueError,
+ Booking.objects.create,
+ start=start,
+ end=end,
+ resource=self.res1,
+ owner=self.user1,
+ config_bundle=self.config_bundle
+ )
+ self.assertRaises(
+ ValueError,
+ Booking.objects.create,
+ start=start + timedelta(days=1),
+ end=end - timedelta(days=1),
+ resource=self.res1,
+ owner=self.user1,
+ config_bundle=self.config_bundle
+ )
+
+ self.assertRaises(
+ ValueError,
+ Booking.objects.create,
+ start=start - timedelta(days=1),
+ end=end,
+ resource=self.res1,
+ owner=self.user1,
+ config_bundle=self.config_bundle
+ )
+
+ self.assertRaises(
+ ValueError,
+ Booking.objects.create,
+ start=start - timedelta(days=1),
+ end=end - timedelta(days=1),
+ resource=self.res1,
+ owner=self.user1,
+ config_bundle=self.config_bundle
+ )
+
+ self.assertRaises(
+ ValueError,
+ Booking.objects.create,
+ start=start,
+ end=end + timedelta(days=1),
+ resource=self.res1,
+ owner=self.user1,
+ config_bundle=self.config_bundle
+ )
+
+ self.assertRaises(
+ ValueError,
+ Booking.objects.create,
+ start=start + timedelta(days=1),
+ end=end + timedelta(days=1),
+ resource=self.res1,
+ owner=self.user1,
+ config_bundle=self.config_bundle
+ )
- self.assertRaises(ValueError, Booking.objects.create, start=start - timedelta(days=1),
- end=end, resource=self.res1, user=self.user1)
- self.assertRaises(ValueError, Booking.objects.create, start=start - timedelta(days=1),
- end=end - timedelta(days=1), resource=self.res1, user=self.user1)
-
- self.assertRaises(ValueError, Booking.objects.create, start=start,
- end=end + timedelta(days=1), resource=self.res1, user=self.user1)
- self.assertRaises(ValueError, Booking.objects.create, start=start + timedelta(days=1),
- end=end + timedelta(days=1), resource=self.res1, user=self.user1)
+ self.assertTrue(
+ Booking.objects.create(
+ start=start - timedelta(days=1),
+ end=start,
+ owner=self.user1,
+ resource=self.res1,
+ config_bundle=self.config_bundle
+ )
+ )
+ self.assertTrue(
+ Booking.objects.create(
+ start=end,
+ end=end + timedelta(days=1),
+ owner=self.user1,
+ resource=self.res1,
+ config_bundle=self.config_bundle
+ )
+ )
- self.assertTrue(Booking.objects.create(start=start - timedelta(days=1), end=start,
- user=self.user1, resource=self.res1))
- self.assertTrue(Booking.objects.create(start=end, end=end + timedelta(days=1),
- user=self.user1, resource=self.res1))
+ self.assertTrue(
+ Booking.objects.create(
+ start=start - timedelta(days=2),
+ end=start - timedelta(days=1),
+ owner=self.user1,
+ resource=self.res1,
+ config_bundle=self.config_bundle
+ )
+ )
self.assertTrue(
- Booking.objects.create(start=start - timedelta(days=2), end=start - timedelta(days=1),
- user=self.user1, resource=self.res1))
+ Booking.objects.create(
+ start=end + timedelta(days=1),
+ end=end + timedelta(days=2),
+ owner=self.user1,
+ resource=self.res1,
+ config_bundle=self.config_bundle
+ )
+ )
+
self.assertTrue(
- Booking.objects.create(start=end + timedelta(days=1), end=end + timedelta(days=2),
- user=self.user1, resource=self.res1))
+ Booking.objects.create(
+ start=start,
+ end=end,
+ owner=self.user1,
+ resource=self.res2,
+ config_bundle=self.config_bundle
+ )
+ )
+
+ def test_extensions(self):
+ """
+ saving a booking with an extended end time is allows to happen twice,
+ and each extension must be a maximum of one week long
+ """
+ start = timezone.now()
+ end = start + timedelta(weeks=1)
self.assertTrue(
- Booking.objects.create(start=start, end=end,
- user=self.user1, resource=self.res2, scenario=self.scenario,
- installer=self.installer)) \ No newline at end of file
+ Booking.objects.create(
+ start=start,
+ end=end,
+ owner=self.user1,
+ resource=self.res1,
+ config_bundle=self.config_bundle
+ )
+ )
+
+ booking = Booking.objects.all().first() # should be only thing in db
+
+ self.assertEquals(booking.ext_count, 2)
+ booking.end = booking.end + timedelta(days=3)
+ try:
+ booking.save()
+ except Exception:
+ self.fail("save() threw an exception")
+ booking.end = booking.end + timedelta(weeks=2)
+ self.assertRaises(ValueError, booking.save)
+ booking.end = booking.end - timedelta(days=8)
+ try:
+ self.assertTrue(booking.save())
+ except Exception:
+ self.fail("save() threw an exception")
+
diff --git a/dashboard/src/booking/tests/test_views.py b/dashboard/src/booking/tests/test_views.py
deleted file mode 100644
index c1da013..0000000
--- a/dashboard/src/booking/tests/test_views.py
+++ /dev/null
@@ -1,106 +0,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
-##############################################################################
-
-
-from datetime import timedelta
-
-from django.test import Client
-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
-
-from account.models import UserProfile
-from booking.models import Booking
-from dashboard.models import Resource
-from jenkins.models import JenkinsSlave
-
-
-class BookingViewTestCase(TestCase):
- def setUp(self):
- self.client = Client()
- 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)
- self.user1 = User.objects.create(username='user1')
- self.user1.set_password('user1')
- self.user1profile = UserProfile.objects.create(user=self.user1)
- self.user1.save()
-
- self.user1 = User.objects.get(pk=self.user1.id)
-
-
- def test_resource_bookings_json(self):
- url = reverse('booking:bookings_json', kwargs={'resource_id': 0})
- self.assertEqual(self.client.get(url).status_code, 404)
-
- url = reverse('booking:bookings_json', kwargs={'resource_id': self.res1.id})
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
- self.assertJSONEqual(force_text(response.content), {"bookings": []})
- booking1 = Booking.objects.create(start=timezone.now(),
- end=timezone.now() + timedelta(weeks=1), user=self.user1,
- resource=self.res1)
- response = self.client.get(url)
- json = response.json()
- self.assertEqual(response.status_code, 200)
- self.assertIn('bookings', json)
- self.assertEqual(len(json['bookings']), 1)
- self.assertIn('start', json['bookings'][0])
- self.assertIn('end', json['bookings'][0])
- self.assertIn('id', json['bookings'][0])
- self.assertIn('purpose', json['bookings'][0])
-
- def test_booking_form_view(self):
- url = reverse('booking:create', kwargs={'resource_id': 0})
- self.assertEqual(self.client.get(url).status_code, 404)
-
- # authenticated user
- url = reverse('booking:create', kwargs={'resource_id': self.res1.id})
- self.client.login(username='user1',password='user1')
- response = self.client.get(url)
- self.assertEqual(response.status_code, 200)
- self.assertTemplateUsed('booking/booking_calendar.html')
- self.assertTemplateUsed('booking/booking_form.html')
- 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/dashboard/src/booking/urls.py b/dashboard/src/booking/urls.py
index ed3b1d4..88fbb0a 100644
--- a/dashboard/src/booking/urls.py
+++ b/dashboard/src/booking/urls.py
@@ -1,5 +1,6 @@
##############################################################################
# Copyright (c) 2016 Max Breitenfeldt and others.
+# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
@@ -27,20 +28,19 @@ from django.conf.urls import url
from booking.views import *
+app_name = "booking"
urlpatterns = [
- url(r'^(?P<resource_id>[0-9]+)/$', BookingFormView.as_view(), name='create'),
- url(r'^(?P<resource_id>[0-9]+)/edit/(?P<booking_id>[0-9]+)/$', BookingEditFormView.as_view(), name='edit'),
- url(r'^(?P<resource_id>[0-9]+)/bookings_json/$', ResourceBookingsJSON.as_view(),
- name='bookings_json'),
- url(r'^detail/$', BookingView.as_view(), name='detail_prefix'),
- url(r'^detail/(?P<booking_id>[0-9]+)/$', BookingView.as_view(), name='detail'),
+ url(r'^detail/(?P<booking_id>[0-9]+)/$', booking_detail_view, name='detail'),
+ url(r'^(?P<booking_id>[0-9]+)/$', booking_detail_view, name='booking_detail'),
url(r'^delete/$', BookingDeleteView.as_view(), name='delete_prefix'),
url(r'^delete/(?P<booking_id>[0-9]+)/$', BookingDeleteView.as_view(), name='delete'),
url(r'^delete/(?P<booking_id>[0-9]+)/confirm/$', bookingDelete, name='delete_booking'),
- url(r'^list/$', BookingListView.as_view(), name='list')
-] \ No newline at end of file
+ url(r'^list/$', BookingListView.as_view(), name='list'),
+ url(r'^stats/$', booking_stats_view, name='stats'),
+ url(r'^stats/json$', booking_stats_json, name='stats_json'),
+]
diff --git a/dashboard/src/booking/views.py b/dashboard/src/booking/views.py
index a52cfe2..c139b4c 100644
--- a/dashboard/src/booking/views.py
+++ b/dashboard/src/booking/views.py
@@ -1,5 +1,6 @@
##############################################################################
# Copyright (c) 2016 Max Breitenfeldt and others.
+# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
#
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Apache License, Version 2.0
@@ -7,39 +8,38 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-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.http import JsonResponse
from django.urls import reverse
from django.utils import timezone
from django.views import View
from django.views.generic import FormView
from django.views.generic import TemplateView
-from jira import JIRAError
-from django.shortcuts import redirect
+from django.shortcuts import redirect, render
+import json
-from account.jira_util import get_jira
from booking.forms import BookingForm, BookingEditForm
-from booking.models import Booking
-from dashboard.models import Resource
-
-def create_jira_ticket(user, booking):
- jira = get_jira(user)
- issue_dict = {
- 'project': 'PHAROS',
- 'summary': str(booking.resource) + ': Access Request',
- 'description': booking.purpose,
- 'issuetype': {'name': 'Task'},
- 'components': [{'name': 'POD Access Request'}],
- 'assignee': {'name': booking.resource.owner.username}
- }
- issue = jira.create_issue(fields=issue_dict)
- jira.add_attachment(issue, user.userprofile.pgp_public_key)
- jira.add_attachment(issue, user.userprofile.ssh_public_key)
- booking.jira_issue_id = issue.id
- booking.save()
+from resource_inventory.models import ResourceBundle
+from resource_inventory.resource_manager import ResourceManager
+from booking.models import Booking, Installer, Opsys
+from booking.stats import StatisticsManager
+
+
+def drop_filter(context):
+ installer_filter = {}
+ for os in Opsys.objects.all():
+ installer_filter[os.id] = []
+ for installer in os.sup_installers.all():
+ installer_filter[os.id].append(installer.id)
+
+ scenario_filter = {}
+ for installer in Installer.objects.all():
+ scenario_filter[installer.id] = []
+ for scenario in installer.sup_scenarios.all():
+ scenario_filter[installer.id].append(scenario.id)
+
+ context.update({'installer_filter': json.dumps(installer_filter), 'scenario_filter': json.dumps(scenario_filter)})
class BookingFormView(FormView):
@@ -47,14 +47,16 @@ class BookingFormView(FormView):
form_class = BookingForm
def dispatch(self, request, *args, **kwargs):
- self.resource = get_object_or_404(Resource, id=self.kwargs['resource_id'])
+ self.resource = get_object_or_404(ResourceBundle, id=self.kwargs['resource_id'])
return super(BookingFormView, self).dispatch(request, *args, **kwargs)
def get_context_data(self, **kwargs):
- title = 'Booking: ' + self.resource.name
+ title = 'Booking: ' + str(self.resource.id)
context = super(BookingFormView, self).get_context_data(**kwargs)
context.update({'title': title, 'resource': self.resource})
- #raise PermissionDenied('check')
+
+ drop_filter(context)
+
return context
def get_success_url(self):
@@ -75,24 +77,16 @@ class BookingFormView(FormView):
booking = Booking(start=form.cleaned_data['start'],
end=form.cleaned_data['end'],
purpose=form.cleaned_data['purpose'],
- opsys=form.cleaned_data['opsys'],
installer=form.cleaned_data['installer'],
scenario=form.cleaned_data['scenario'],
- resource=self.resource, user=user)
+ resource=self.resource,
+ owner=user
+ )
try:
booking.save()
except ValueError 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)
- except JIRAError:
- messages.add_message(self.request, messages.ERROR, 'Failed to create Jira Ticket. '
- 'Please check your Jira '
- 'permissions.')
- booking.delete()
- return super(BookingFormView, self).form_invalid(form)
messages.add_message(self.request, messages.SUCCESS, 'Booking saved')
return super(BookingFormView, self).form_valid(form)
@@ -105,7 +99,7 @@ class BookingEditFormView(FormView):
return True
def dispatch(self, request, *args, **kwargs):
- self.resource = get_object_or_404(Resource, id=self.kwargs['resource_id'])
+ self.resource = get_object_or_404(ResourceBundle, id=self.kwargs['resource_id'])
self.original_booking = get_object_or_404(Booking, id=self.kwargs['booking_id'])
return super(BookingEditFormView, self).dispatch(request, *args, **kwargs)
@@ -113,6 +107,9 @@ class BookingEditFormView(FormView):
title = 'Editing Booking on: ' + self.resource.name
context = super(BookingEditFormView, self).get_context_data(**kwargs)
context.update({'title': title, 'resource': self.resource, 'booking': self.original_booking})
+
+ drop_filter(context)
+
return context
def get_form_kwargs(self):
@@ -120,14 +117,6 @@ class BookingEditFormView(FormView):
kwargs['purpose'] = self.original_booking.purpose
kwargs['start'] = self.original_booking.start
kwargs['end'] = self.original_booking.end
- try:
- kwargs['installer'] = self.original_booking.installer
- except AttributeError:
- pass
- try:
- kwargs['scenario'] = self.original_booking.scenario
- except AttributeError:
- pass
return kwargs
def get_success_url(self):
@@ -145,7 +134,7 @@ class BookingEditFormView(FormView):
'You are not the owner of this booking.')
return super(BookingEditFormView, self).form_invalid(form)
- #Do Conflict Checks
+ # Do Conflict Checks
if self.original_booking.end != form.cleaned_data['end']:
if form.cleaned_data['end'] - self.original_booking.end > timezone.timedelta(days=7):
messages.add_message(self.request, messages.ERROR,
@@ -176,13 +165,12 @@ class BookingEditFormView(FormView):
messages.add_message(self.request, messages.ERROR, err)
return super(BookingEditFormView, self).form_invalid(form)
- user = self.request.user
return super(BookingEditFormView, self).form_valid(form)
+
class BookingView(TemplateView):
template_name = "booking/booking_detail.html"
-
def get_context_data(self, **kwargs):
booking = get_object_or_404(Booking, id=self.kwargs['booking_id'])
title = 'Booking Details'
@@ -190,6 +178,7 @@ class BookingView(TemplateView):
context.update({'title': title, 'booking': booking})
return context
+
class BookingDeleteView(TemplateView):
template_name = "booking/booking_delete.html"
@@ -200,12 +189,14 @@ class BookingDeleteView(TemplateView):
context.update({'title': title, 'booking': booking})
return context
+
def bookingDelete(request, booking_id):
booking = get_object_or_404(Booking, id=booking_id)
booking.delete()
messages.add_message(request, messages.SUCCESS, 'Booking deleted')
return redirect('../../../../')
+
class BookingListView(TemplateView):
template_name = "booking/booking_list.html"
@@ -219,8 +210,47 @@ class BookingListView(TemplateView):
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', 'opsys__name',
- 'installer__name', 'scenario__name')
+ resource = get_object_or_404(ResourceBundle, id=self.kwargs['resource_id'])
+ bookings = resource.booking_set.get_queryset().values(
+ 'id',
+ 'start',
+ 'end',
+ 'purpose',
+ 'jira_issue_status',
+ 'config_bundle__name'
+ )
return JsonResponse({'bookings': list(bookings)})
+
+
+def booking_detail_view(request, booking_id):
+ user = None
+ if request.user.is_authenticated:
+ user = request.user
+ else:
+ return render(request, "dashboard/login.html", {'title': 'Authentication Required'})
+
+ booking = get_object_or_404(Booking, id=booking_id)
+ return render(request, "booking/booking_detail.html", {
+ 'title': 'Booking Details',
+ 'booking': booking,
+ 'pdf': ResourceManager().makePDF(booking.resource),
+ 'user_id': user.id})
+
+
+def booking_stats_view(request):
+ return render(
+ request,
+ "booking/stats.html",
+ context={
+ "data": StatisticsManager.getContinuousBookingTimeSeries(),
+ "title": "Booking Statistics"
+ }
+ )
+
+
+def booking_stats_json(request):
+ try:
+ span = int(request.GET.get("days", 14))
+ except:
+ span = 14
+ return JsonResponse(StatisticsManager.getContinuousBookingTimeSeries(span), safe=False)