Split the dashboard into different apps, add tests 69/19069/1
authormaxbr <maxbr@mi.fu-berlin.de>
Fri, 19 Aug 2016 15:10:31 +0000 (17:10 +0200)
committermaxbr <maxbr@mi.fu-berlin.de>
Fri, 19 Aug 2016 15:10:31 +0000 (17:10 +0200)
JIRA: RELENG-12

Signed-off-by: maxbr <maxbr@mi.fu-berlin.de>
81 files changed:
tools/pharos-dashboard/README.md [deleted file]
tools/pharos-dashboard/TODO [deleted file]
tools/pharos-dashboard/account/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/account/admin.py [new file with mode: 0644]
tools/pharos-dashboard/account/apps.py [new file with mode: 0644]
tools/pharos-dashboard/account/forms.py [new file with mode: 0644]
tools/pharos-dashboard/account/middleware.py [new file with mode: 0644]
tools/pharos-dashboard/account/migrations/0001_initial.py [new file with mode: 0644]
tools/pharos-dashboard/account/migrations/0002_userprofile_timezone.py [new file with mode: 0644]
tools/pharos-dashboard/account/migrations/0003_userprofile_registration_complete.py [new file with mode: 0644]
tools/pharos-dashboard/account/migrations/0004_auto_20160812_1805.py [new file with mode: 0644]
tools/pharos-dashboard/account/migrations/0005_remove_userprofile_registration_complete.py [new file with mode: 0644]
tools/pharos-dashboard/account/migrations/0006_auto_20160813_1443.py [new file with mode: 0644]
tools/pharos-dashboard/account/migrations/0007_auto_20160814_1056.py [new file with mode: 0644]
tools/pharos-dashboard/account/migrations/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/account/models.py [new file with mode: 0644]
tools/pharos-dashboard/account/tests/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/account/tests/test_general.py [new file with mode: 0644]
tools/pharos-dashboard/account/urls.py [new file with mode: 0644]
tools/pharos-dashboard/account/views.py [new file with mode: 0644]
tools/pharos-dashboard/booking/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/booking/admin.py [new file with mode: 0644]
tools/pharos-dashboard/booking/apps.py [new file with mode: 0644]
tools/pharos-dashboard/booking/forms.py [new file with mode: 0644]
tools/pharos-dashboard/booking/migrations/0001_initial.py [new file with mode: 0644]
tools/pharos-dashboard/booking/migrations/0002_remove_booking_deleted.py [new file with mode: 0644]
tools/pharos-dashboard/booking/migrations/0003_remove_booking_status.py [new file with mode: 0644]
tools/pharos-dashboard/booking/migrations/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/booking/models.py [new file with mode: 0644]
tools/pharos-dashboard/booking/tests/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/booking/tests/test_models.py [new file with mode: 0644]
tools/pharos-dashboard/booking/tests/test_views.py [new file with mode: 0644]
tools/pharos-dashboard/booking/urls.py [new file with mode: 0644]
tools/pharos-dashboard/booking/views.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/admin.py
tools/pharos-dashboard/dashboard/fixtures/DBdata_resources.json [deleted file]
tools/pharos-dashboard/dashboard/fixtures/DBdata_test_bookings.json [deleted file]
tools/pharos-dashboard/dashboard/fixtures/DBdata_users.json [deleted file]
tools/pharos-dashboard/dashboard/fixtures/dashboard.json [new file with mode: 0644]
tools/pharos-dashboard/dashboard/forms/booking_form.py [deleted file]
tools/pharos-dashboard/dashboard/jenkins/jenkins_util.py [deleted file]
tools/pharos-dashboard/dashboard/migrations/0001_initial.py
tools/pharos-dashboard/dashboard/migrations/0002_resourceutilization.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/migrations/0003_auto_20160813_1302.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/migrations/0004_resource_owners.py [new file with mode: 0644]
tools/pharos-dashboard/dashboard/models.py
tools/pharos-dashboard/dashboard/static/js/booking-calendar.js [deleted file]
tools/pharos-dashboard/dashboard/static/js/csrf.js [deleted file]
tools/pharos-dashboard/dashboard/static/js/datetimepicker-options.js [deleted file]
tools/pharos-dashboard/dashboard/static/js/fullcalendar-options.js [deleted file]
tools/pharos-dashboard/dashboard/urls.py
tools/pharos-dashboard/dashboard/views.py [moved from tools/pharos-dashboard/dashboard/views/table_views.py with 73% similarity]
tools/pharos-dashboard/dashboard/views/booking.py [deleted file]
tools/pharos-dashboard/dashboard/views/registration.py [deleted file]
tools/pharos-dashboard/issues.org [deleted file]
tools/pharos-dashboard/jenkins/__init__.py [new file with mode: 0644]
tools/pharos-dashboard/jenkins/adapter.py [moved from tools/pharos-dashboard/dashboard/jenkins/jenkins_adapter.py with 55% similarity]
tools/pharos-dashboard/jenkins/apps.py [new file with mode: 0644]
tools/pharos-dashboard/jenkins/tests.py [moved from tools/pharos-dashboard/dashboard/tests.py with 91% similarity]
tools/pharos-dashboard/manage.py
tools/pharos-dashboard/pharos_dashboard/settings.py
tools/pharos-dashboard/pharos_dashboard/urls.py
tools/pharos-dashboard/pharos_dashboard/wsgi.py
tools/pharos-dashboard/requirements.txt [deleted file]
tools/pharos-dashboard/static/bower.json [moved from tools/pharos-dashboard/dashboard/static/bower.json with 100% similarity]
tools/pharos-dashboard/static/css/theme.css [moved from tools/pharos-dashboard/dashboard/static/css/theme.css with 100% similarity]
tools/pharos-dashboard/static/js/booking-calendar.js [new file with mode: 0644]
tools/pharos-dashboard/static/js/dataTables-sort.js [moved from tools/pharos-dashboard/dashboard/static/js/dataTables-sort.js with 100% similarity]
tools/pharos-dashboard/static/js/datetimepicker-options.js [new file with mode: 0644]
tools/pharos-dashboard/static/js/fullcalendar-options.js [new file with mode: 0644]
tools/pharos-dashboard/templates/base.html [moved from tools/pharos-dashboard/templates/dashboard/base.html with 79% similarity]
tools/pharos-dashboard/templates/booking/booking_calendar.html [new file with mode: 0644]
tools/pharos-dashboard/templates/booking/booking_form.html [new file with mode: 0644]
tools/pharos-dashboard/templates/dashboard/booking_calendar.html [deleted file]
tools/pharos-dashboard/templates/dashboard/ci_pods.html [moved from tools/pharos-dashboard/templates/tables/ci_pods.html with 88% similarity]
tools/pharos-dashboard/templates/dashboard/dev_pods.html [moved from tools/pharos-dashboard/templates/tables/dev_pods.html with 61% similarity]
tools/pharos-dashboard/templates/dashboard/jenkins_slaves.html [moved from tools/pharos-dashboard/templates/tables/jenkins_slaves.html with 100% similarity]
tools/pharos-dashboard/templates/dashboard/table.html
tools/pharos-dashboard/templates/layout.html [moved from tools/pharos-dashboard/templates/layout/base.html with 85% similarity]
tools/pharos-dashboard/templates/registration/login.html
tools/pharos-dashboard/templates/registration/registration_form.html [new file with mode: 0644]

diff --git a/tools/pharos-dashboard/README.md b/tools/pharos-dashboard/README.md
deleted file mode 100644 (file)
index be27bf5..0000000
+++ /dev/null
@@ -1 +0,0 @@
-# Pharos Dashboard
diff --git a/tools/pharos-dashboard/TODO b/tools/pharos-dashboard/TODO
deleted file mode 100644 (file)
index 1c539d6..0000000
+++ /dev/null
@@ -1,3 +0,0 @@
-- implement ajax booking form to call from fullcalendar, then implement editing of multiple events
-- if the user is behind a VPN, his timezone settings might be wrong, there should be an option in the
-user settings to override the browser timezone.
diff --git a/tools/pharos-dashboard/account/__init__.py b/tools/pharos-dashboard/account/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/pharos-dashboard/account/admin.py b/tools/pharos-dashboard/account/admin.py
new file mode 100644 (file)
index 0000000..7fab123
--- /dev/null
@@ -0,0 +1,5 @@
+from django.contrib import admin
+
+from account.models import UserProfile
+
+admin.site.register(UserProfile)
\ No newline at end of file
diff --git a/tools/pharos-dashboard/account/apps.py b/tools/pharos-dashboard/account/apps.py
new file mode 100644 (file)
index 0000000..999566c
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class AccountsConfig(AppConfig):
+    name = 'account'
diff --git a/tools/pharos-dashboard/account/forms.py b/tools/pharos-dashboard/account/forms.py
new file mode 100644 (file)
index 0000000..7893867
--- /dev/null
@@ -0,0 +1,17 @@
+import django.forms as forms
+import pytz as pytz
+
+from registration.forms import RegistrationForm as BaseRegistrationForm
+
+
+class AccountSettingsForm(forms.Form):
+    fields = ['first_name', 'last_name', 'email', 'company', 'ssh_public_key', 'pgp_public_key',
+              'timezone']
+
+    first_name = forms.CharField(max_length=30)
+    last_name = forms.CharField(max_length=30)
+    email = forms.EmailField()
+    company = forms.CharField(max_length=30)
+    ssh_public_key = forms.CharField(max_length=2048, widget=forms.Textarea)
+    pgp_public_key = forms.CharField(max_length=2048, widget=forms.Textarea)
+    timezone = forms.ChoiceField(choices=[(x, x) for x in pytz.common_timezones], initial='UTC')
\ No newline at end of file
diff --git a/tools/pharos-dashboard/account/middleware.py b/tools/pharos-dashboard/account/middleware.py
new file mode 100644 (file)
index 0000000..f5170ba
--- /dev/null
@@ -0,0 +1,15 @@
+from django.core.exceptions import ObjectDoesNotExist
+from django.utils import timezone
+from django.utils.deprecation import MiddlewareMixin
+
+
+class TimezoneMiddleware(MiddlewareMixin):
+    """
+    Activate the timezone from request.user.userprofile if user is authenticated,
+    deactivate the timezone otherwise and use default (UTC)
+    """
+    def process_request(self, request):
+        if request.user.is_authenticated:
+            timezone.activate(request.user.userprofile.timezone)
+        else:
+            timezone.deactivate()
diff --git a/tools/pharos-dashboard/account/migrations/0001_initial.py b/tools/pharos-dashboard/account/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..752d517
--- /dev/null
@@ -0,0 +1,43 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-12 09:51
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('dashboard', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='UserProfile',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('sshkey', models.CharField(max_length=1024)),
+                ('company', models.CharField(max_length=200)),
+                ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'db_table': 'user_profile',
+            },
+        ),
+        migrations.CreateModel(
+            name='UserResource',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('resource', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='dashboard.Resource')),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'db_table': 'user_resource',
+            },
+        ),
+    ]
diff --git a/tools/pharos-dashboard/account/migrations/0002_userprofile_timezone.py b/tools/pharos-dashboard/account/migrations/0002_userprofile_timezone.py
new file mode 100644 (file)
index 0000000..ea67598
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-12 13:17
+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='timezone',
+            field=models.CharField(default='UTC', max_length=100),
+        ),
+    ]
diff --git a/tools/pharos-dashboard/account/migrations/0003_userprofile_registration_complete.py b/tools/pharos-dashboard/account/migrations/0003_userprofile_registration_complete.py
new file mode 100644 (file)
index 0000000..d611443
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-12 17:09
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('account', '0002_userprofile_timezone'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='userprofile',
+            name='registration_complete',
+            field=models.BooleanField(default=False),
+        ),
+    ]
diff --git a/tools/pharos-dashboard/account/migrations/0004_auto_20160812_1805.py b/tools/pharos-dashboard/account/migrations/0004_auto_20160812_1805.py
new file mode 100644 (file)
index 0000000..cb22855
--- /dev/null
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-12 18:05
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('account', '0003_userprofile_registration_complete'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='userprofile',
+            name='gpgkey',
+            field=models.CharField(default='a', max_length=2048),
+            preserve_default=False,
+        ),
+        migrations.AlterField(
+            model_name='userprofile',
+            name='sshkey',
+            field=models.CharField(max_length=2048),
+        ),
+    ]
diff --git a/tools/pharos-dashboard/account/migrations/0005_remove_userprofile_registration_complete.py b/tools/pharos-dashboard/account/migrations/0005_remove_userprofile_registration_complete.py
new file mode 100644 (file)
index 0000000..fa4bf72
--- /dev/null
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-13 10:49
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('account', '0004_auto_20160812_1805'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='userprofile',
+            name='registration_complete',
+        ),
+    ]
diff --git a/tools/pharos-dashboard/account/migrations/0006_auto_20160813_1443.py b/tools/pharos-dashboard/account/migrations/0006_auto_20160813_1443.py
new file mode 100644 (file)
index 0000000..7d9cd58
--- /dev/null
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-13 14:43
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('account', '0005_remove_userprofile_registration_complete'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='userresource',
+            name='resource',
+        ),
+        migrations.RemoveField(
+            model_name='userresource',
+            name='user',
+        ),
+        migrations.DeleteModel(
+            name='UserResource',
+        ),
+    ]
diff --git a/tools/pharos-dashboard/account/migrations/0007_auto_20160814_1056.py b/tools/pharos-dashboard/account/migrations/0007_auto_20160814_1056.py
new file mode 100644 (file)
index 0000000..2d1bbae
--- /dev/null
@@ -0,0 +1,20 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-14 10:56
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('account', '0006_auto_20160813_1443'),
+    ]
+
+    operations = [
+        migrations.RenameField(
+            model_name='userprofile',
+            old_name='gpgkey',
+            new_name='pgpkey',
+        ),
+    ]
diff --git a/tools/pharos-dashboard/account/migrations/__init__.py b/tools/pharos-dashboard/account/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/pharos-dashboard/account/models.py b/tools/pharos-dashboard/account/models.py
new file mode 100644 (file)
index 0000000..5181c71
--- /dev/null
@@ -0,0 +1,16 @@
+from django.db import models
+
+from django.contrib.auth.models import User
+
+from dashboard.models import Resource
+
+
+class UserProfile(models.Model):
+    user = models.OneToOneField(User, on_delete=models.CASCADE)
+    timezone = models.CharField(max_length=100, blank=False, default='UTC')
+    sshkey = models.CharField(max_length=2048, blank=False)
+    pgpkey = models.CharField(max_length=2048, blank=False)
+    company = models.CharField(max_length=200, blank=False)
+
+    class Meta:
+        db_table = 'user_profile'
diff --git a/tools/pharos-dashboard/account/tests/__init__.py b/tools/pharos-dashboard/account/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/pharos-dashboard/account/tests/test_general.py b/tools/pharos-dashboard/account/tests/test_general.py
new file mode 100644 (file)
index 0000000..ba80b62
--- /dev/null
@@ -0,0 +1,40 @@
+from django.contrib.auth.models import User
+from django.test import Client
+from django.test import TestCase
+from django.urls import reverse
+from django.utils import timezone
+
+from account.models import UserProfile
+
+
+class AccountMiddlewareTestCase(TestCase):
+    def setUp(self):
+        self.client = Client()
+        self.user1 = User.objects.create(username='user1')
+        self.user1.set_password('user1')
+        self.user1profile = UserProfile.objects.create(user=self.user1)
+        self.user1.save()
+
+    def test_timezone_middleware(self):
+        """
+        The timezone should be UTC for anonymous users, for authenticated users it should be set
+        to user.userprofile.timezone
+        """
+        #default
+        self.assertEqual(timezone.get_current_timezone_name(), 'UTC')
+
+        url = reverse('account:settings')
+        # anonymous request
+        self.client.get(url)
+        self.assertEqual(timezone.get_current_timezone_name(), 'UTC')
+
+        # authenticated user with UTC timezone (userprofile default)
+        self.client.login(username='user1', password='user1')
+        self.client.get(url)
+        self.assertEqual(timezone.get_current_timezone_name(), 'UTC')
+
+        # authenticated user with custom timezone (userprofile default)
+        self.user1profile.timezone = 'Etc/Greenwich'
+        self.user1profile.save()
+        self.client.get(url)
+        self.assertEqual(timezone.get_current_timezone_name(), 'Etc/Greenwich')
diff --git a/tools/pharos-dashboard/account/urls.py b/tools/pharos-dashboard/account/urls.py
new file mode 100644 (file)
index 0000000..5d68135
--- /dev/null
@@ -0,0 +1,26 @@
+"""pharos_dashboard URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/1.10/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.conf.urls import url, include
+    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
+"""
+from django.conf.urls import url
+from django.contrib.auth import views as auth_views
+
+from account.views import *
+
+urlpatterns = [
+    url(r'^login/$', auth_views.login, name='login'),
+    url(r'^logout/$', auth_views.logout, name='logout'),
+    url(r'^register/', RegistrationView.as_view(), name='registration'),
+    url(r'^settings/', AccountSettingsView.as_view(), name='settings'),
+]
diff --git a/tools/pharos-dashboard/account/views.py b/tools/pharos-dashboard/account/views.py
new file mode 100644 (file)
index 0000000..3432867
--- /dev/null
@@ -0,0 +1,78 @@
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required
+from django.core.exceptions import ObjectDoesNotExist
+from django.urls import reverse
+from django.utils.decorators import method_decorator
+from django.views.generic import FormView
+from registration.backends.simple.views import RegistrationView as BaseRegistrationView
+
+from account.forms import AccountSettingsForm
+from account.models import UserProfile
+
+
+class RegistrationView(BaseRegistrationView):
+    template_name = 'registration/registration_form.html'
+
+    def get_context_data(self, **kwargs):
+        context = super(RegistrationView, self).get_context_data(**kwargs)
+        context.update({'title': "Registration"})
+        return context
+
+    def register(self, form):
+        new_user = super(RegistrationView, self).register(form)
+        UserProfile.objects.create(user=new_user)
+        messages.add_message(self.request, messages.INFO, 'Please complete your user profile.')
+        return new_user
+
+    def get_success_url(self, user):
+        return reverse('account:settings')
+
+
+@method_decorator(login_required, name='dispatch')
+class AccountSettingsView(FormView):
+    form_class = AccountSettingsForm
+    template_name = 'registration/registration_form.html'
+    success_url = '/'
+
+    def dispatch(self, request, *args, **kwargs):
+        try:
+            request.user.userprofile
+        except ObjectDoesNotExist:
+            UserProfile.objects.create(user=request.user)
+            messages.add_message(self.request, messages.INFO,
+                                 'Please complete your user profile to proceed.')
+        return super(AccountSettingsView, self).dispatch(request, *args, **kwargs)
+
+    def get_context_data(self, **kwargs):
+        context = super(AccountSettingsView, self).get_context_data(**kwargs)
+        context.update({'title': "Settings"})
+        return context
+
+    def get_initial(self):
+        user = self.request.user
+        initial = super(AccountSettingsView, self).get_initial()
+        initial['first_name'] = user.first_name
+        initial['last_name'] = user.last_name
+        initial['email'] = user.email
+        initial['company'] = user.userprofile.company
+        initial['ssh_public_key'] = user.userprofile.sshkey
+        initial['pgp_public_key'] = user.userprofile.pgpkey
+        initial['timezone'] = user.userprofile.timezone
+        return initial
+
+    def form_valid(self, form):
+        user = self.request.user
+        user.first_name = form.cleaned_data['first_name']
+        user.last_name = form.cleaned_data['last_name']
+        user.email = form.cleaned_data['email']
+        user.userprofile.company = form.cleaned_data['company']
+        user.userprofile.sshkey = form.cleaned_data['ssh_public_key']
+        user.userprofile.pgpkey = form.cleaned_data['pgp_public_key']
+        user.userprofile.timezone = form.cleaned_data['timezone']
+        user.userprofile.save()
+        if not user.is_active:
+            user.is_active = True
+        user.save()
+        messages.add_message(self.request, messages.INFO,
+                             'Settings saved')
+        return super(AccountSettingsView, self).form_valid(form)
diff --git a/tools/pharos-dashboard/booking/__init__.py b/tools/pharos-dashboard/booking/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/pharos-dashboard/booking/admin.py b/tools/pharos-dashboard/booking/admin.py
new file mode 100644 (file)
index 0000000..6055bed
--- /dev/null
@@ -0,0 +1,5 @@
+from django.contrib import admin
+
+from booking.models import Booking
+
+admin.site.register(Booking)
\ No newline at end of file
diff --git a/tools/pharos-dashboard/booking/apps.py b/tools/pharos-dashboard/booking/apps.py
new file mode 100644 (file)
index 0000000..2d5f36f
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class BookingConfig(AppConfig):
+    name = 'booking'
diff --git a/tools/pharos-dashboard/booking/forms.py b/tools/pharos-dashboard/booking/forms.py
new file mode 100644 (file)
index 0000000..5b32c86
--- /dev/null
@@ -0,0 +1,9 @@
+import django.forms as forms
+
+
+class BookingForm(forms.Form):
+    fields = ['start', 'end', 'purpose']
+
+    start = forms.DateTimeField()
+    end = forms.DateTimeField()
+    purpose = forms.CharField(max_length=300)
\ No newline at end of file
diff --git a/tools/pharos-dashboard/booking/migrations/0001_initial.py b/tools/pharos-dashboard/booking/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..57735ee
--- /dev/null
@@ -0,0 +1,36 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-12 09:51
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('dashboard', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Booking',
+            fields=[
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('deleted', models.BooleanField(default=False)),
+                ('start', models.DateTimeField()),
+                ('end', models.DateTimeField()),
+                ('status', models.CharField(max_length=20)),
+                ('purpose', models.CharField(max_length=300)),
+                ('resource', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='dashboard.Resource')),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'db_table': 'booking',
+            },
+        ),
+    ]
diff --git a/tools/pharos-dashboard/booking/migrations/0002_remove_booking_deleted.py b/tools/pharos-dashboard/booking/migrations/0002_remove_booking_deleted.py
new file mode 100644 (file)
index 0000000..335379d
--- /dev/null
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-13 12:50
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('booking', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='booking',
+            name='deleted',
+        ),
+    ]
diff --git a/tools/pharos-dashboard/booking/migrations/0003_remove_booking_status.py b/tools/pharos-dashboard/booking/migrations/0003_remove_booking_status.py
new file mode 100644 (file)
index 0000000..95089a7
--- /dev/null
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-13 12:51
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('booking', '0002_remove_booking_deleted'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='booking',
+            name='status',
+        ),
+    ]
diff --git a/tools/pharos-dashboard/booking/migrations/__init__.py b/tools/pharos-dashboard/booking/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/pharos-dashboard/booking/models.py b/tools/pharos-dashboard/booking/models.py
new file mode 100644 (file)
index 0000000..719dd9b
--- /dev/null
@@ -0,0 +1,53 @@
+from django.contrib.auth.models import User
+from django.db import models
+
+from dashboard.models import Resource
+
+
+class Booking(models.Model):
+    id = models.AutoField(primary_key=True)
+    user = models.ForeignKey(User, models.CASCADE)  # delete if user is deleted
+    resource = models.ForeignKey(Resource, models.PROTECT)
+    start = models.DateTimeField()
+    end = models.DateTimeField()
+
+    purpose = models.CharField(max_length=300, blank=False)
+
+    class Meta:
+        db_table = 'booking'
+
+    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 in self.resource.owners.all():
+            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
+        conflicting_dates = Booking.objects.filter(resource=self.resource)
+        conflicting_dates = conflicting_dates.filter(end__gt=self.start)
+        conflicting_dates = conflicting_dates.filter(start__lt=self.end)
+        if conflicting_dates.count() > 0:
+            raise ValueError('This booking overlaps with another booking')
+        return super(Booking, self).save(*args, **kwargs)
+
+
+    def __str__(self):
+        return str(self.resource) + ' from ' + str(self.start) + ' until ' + str(self.end)
diff --git a/tools/pharos-dashboard/booking/tests/__init__.py b/tools/pharos-dashboard/booking/tests/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/tools/pharos-dashboard/booking/tests/test_models.py b/tools/pharos-dashboard/booking/tests/test_models.py
new file mode 100644 (file)
index 0000000..e933f6e
--- /dev/null
@@ -0,0 +1,88 @@
+from datetime import timedelta
+
+from django.contrib.auth.models import User, Permission
+from django.test import TestCase
+from django.utils import timezone
+
+from booking.models import Booking
+from dashboard.models import Resource
+
+
+class BookingModelTestCase(TestCase):
+    def setUp(self):
+        self.res1 = Resource.objects.create(name='res1', slavename='s1', description='x', url='x')
+        self.res2 = Resource.objects.create(name='res2', slavename='s2', description='x', url='x')
+
+        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)
+
+    def test_start__end(self):
+        """
+        if the start of a booking is greater or equal then the end, saving should raise a
+        ValueException
+        """
+        start = timezone.now()
+        end = start - timedelta(weeks=1)
+        self.assertRaises(ValueError, Booking.objects.create, start=start, end=end,
+                          resource=self.res1, user=self.user1)
+        end = start
+        self.assertRaises(ValueError, Booking.objects.create, start=start, end=end,
+                          resource=self.res1, user=self.user1)
+
+    def test_conflicts(self):
+        """
+        saving an overlapping booking on the same resource should raise a ValueException
+        saving for different resources should succeed
+        """
+        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)
+
+        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,
+                                               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),
+                                   user=self.user1, resource=self.res1))
+        self.assertTrue(
+            Booking.objects.create(start=end + timedelta(days=1), end=end + timedelta(days=2),
+                                   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')
+        self.assertRaises(PermissionError, Booking.objects.create, start=timezone.now(),
+                          end=timezone.now() + timedelta(days=1), resource=self.res1, user=user)
+        self.res1.owners.add(user)
+        self.assertTrue(
+            Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1),
+                                   resource=self.res1, user=user))
+        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))
diff --git a/tools/pharos-dashboard/booking/tests/test_views.py b/tools/pharos-dashboard/booking/tests/test_views.py
new file mode 100644 (file)
index 0000000..f5b75d1
--- /dev/null
@@ -0,0 +1,72 @@
+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.encoding import force_text
+from registration.forms import User
+
+from account.models import UserProfile
+from booking.models import Booking
+from dashboard.models import Resource
+
+
+class BookingViewTestCase(TestCase):
+    def setUp(self):
+        self.client = Client()
+        self.res1 = Resource.objects.create(name='res1', slavename='s1', description='x', url='x')
+        self.user1 = User.objects.create(username='user1')
+        self.user1.set_password('user1')
+        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)
+
+
+    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)
+
+        # anonymous user
+        url = reverse('booking:create', kwargs={'resource_id': self.res1.id})
+        response = self.client.get(url, follow=True)
+        self.assertRedirects(response, reverse('account:login') + '?next=/booking/' + str(
+            self.res1.id) + '/')
+
+        # authenticated user
+        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)
+
+
+
diff --git a/tools/pharos-dashboard/booking/urls.py b/tools/pharos-dashboard/booking/urls.py
new file mode 100644 (file)
index 0000000..37f0c6b
--- /dev/null
@@ -0,0 +1,24 @@
+"""pharos_dashboard URL Configuration
+
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/1.10/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.conf.urls import url, include
+    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
+"""
+from django.conf.urls import url
+
+from booking.views import *
+
+urlpatterns = [
+    url(r'^(?P<resource_id>[0-9]+)/$', BookingFormView.as_view(), name='create'),
+    url(r'^(?P<resource_id>[0-9]+)/bookings_json/$', ResourceBookingsJSON.as_view(),
+        name='bookings_json'),
+]
diff --git a/tools/pharos-dashboard/booking/views.py b/tools/pharos-dashboard/booking/views.py
new file mode 100644 (file)
index 0000000..bc00d3e
--- /dev/null
@@ -0,0 +1,50 @@
+from django.contrib import messages
+from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin
+from django.http import JsonResponse
+from django.shortcuts import get_object_or_404
+from django.urls import reverse
+from django.views import View
+from django.views.generic import FormView
+
+from booking.forms import BookingForm
+from booking.models import Booking
+from dashboard.models import Resource
+
+class BookingFormView(LoginRequiredMixin, FormView):
+    template_name = "booking/booking_calendar.html"
+    form_class = BookingForm
+
+    def dispatch(self, request, *args, **kwargs):
+        self.resource = get_object_or_404(Resource, id=self.kwargs['resource_id'])
+        return super(BookingFormView, self).dispatch(request,*args, **kwargs)
+
+    def get_context_data(self, **kwargs):
+        title = 'Booking: ' + self.resource.name
+        context = super(BookingFormView, self).get_context_data(**kwargs)
+        context.update({'title': title, 'resource': self.resource})
+        return context
+
+    def get_success_url(self):
+        return reverse('booking:create', kwargs=self.kwargs)
+
+    def form_valid(self, form):
+        booking = Booking(start=form.cleaned_data['start'], end=form.cleaned_data['end'],
+                          purpose=form.cleaned_data['purpose'], resource=self.resource,
+                          user=self.request.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)
+        messages.add_message(self.request, messages.SUCCESS, 'Booking saved')
+        return super(BookingFormView, self).form_valid(form)
+
+
+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')
+        return JsonResponse({'bookings': list(bookings)})
index 532173f..990e63e 100644 (file)
@@ -1,9 +1,5 @@
 from django.contrib import admin
-from .models import *
 
-# Register your models here.
-admin.site.register(Booking)
-admin.site.register(Pod)
+from dashboard.models import Resource
+
 admin.site.register(Resource)
-admin.site.register(Server)
-admin.site.register(UserResource)
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/fixtures/DBdata_resources.json b/tools/pharos-dashboard/dashboard/fixtures/DBdata_resources.json
deleted file mode 100644 (file)
index 5aaccf7..0000000
+++ /dev/null
@@ -1 +0,0 @@
-[{"model": "dashboard.pod", "pk": 91, "fields": {"resource": 95, "chassis": null}}, {"model": "dashboard.pod", "pk": 92, "fields": {"resource": 96, "chassis": null}}, {"model": "dashboard.pod", "pk": 93, "fields": {"resource": 97, "chassis": null}}, {"model": "dashboard.pod", "pk": 94, "fields": {"resource": 98, "chassis": null}}, {"model": "dashboard.pod", "pk": 95, "fields": {"resource": 99, "chassis": null}}, {"model": "dashboard.pod", "pk": 96, "fields": {"resource": 100, "chassis": null}}, {"model": "dashboard.pod", "pk": 97, "fields": {"resource": 101, "chassis": null}}, {"model": "dashboard.pod", "pk": 98, "fields": {"resource": 102, "chassis": null}}, {"model": "dashboard.pod", "pk": 99, "fields": {"resource": 103, "chassis": null}}, {"model": "dashboard.pod", "pk": 100, "fields": {"resource": 104, "chassis": null}}, {"model": "dashboard.pod", "pk": 101, "fields": {"resource": 105, "chassis": null}}, {"model": "dashboard.pod", "pk": 102, "fields": {"resource": 106, "chassis": null}}, {"model": "dashboard.pod", "pk": 103, "fields": {"resource": 107, "chassis": null}}, {"model": "dashboard.pod", "pk": 104, "fields": {"resource": 108, "chassis": null}}, {"model": "dashboard.pod", "pk": 105, "fields": {"resource": 109, "chassis": null}}, {"model": "dashboard.pod", "pk": 106, "fields": {"resource": 110, "chassis": null}}, {"model": "dashboard.pod", "pk": 107, "fields": {"resource": 111, "chassis": null}}, {"model": "dashboard.pod", "pk": 108, "fields": {"resource": 112, "chassis": null}}, {"model": "dashboard.resource", "pk": 95, "fields": {"name": "Linux Foundation POD 1", "slavename": "lf-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 96, "fields": {"name": "Linux Foundation POD 2", "slavename": "lf-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 97, "fields": {"name": "Ericsson  POD 2", "slavename": "ericsson-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 98, "fields": {"name": "Intel POD 2", "slavename": "intel-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 99, "fields": {"name": "Intel POD 5", "slavename": "intel-pod5", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 100, "fields": {"name": "Intel POD 6", "slavename": "intel-pod6", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 101, "fields": {"name": "Intel POD 8", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 102, "fields": {"name": "Huawei POD 1", "slavename": "huawei-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 103, "fields": {"name": "Intel POD 3", "slavename": "intel-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 104, "fields": {"name": "Dell POD 1", "slavename": "dell-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 105, "fields": {"name": "Dell POD 2", "slavename": "dell-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 106, "fields": {"name": "Orange POD 2", "slavename": "orange-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 107, "fields": {"name": "Arm POD 1", "slavename": "arm-build1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 108, "fields": {"name": "Ericsson  POD 1", "slavename": "ericsson-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 109, "fields": {"name": "Huawei POD 2", "slavename": "huawei-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 110, "fields": {"name": "Huawei POD 3", "slavename": "huawei-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 111, "fields": {"name": "Huawei POD 4", "slavename": "huawei-pod4", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 112, "fields": {"name": "Intel POD 9", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9", "bookable": false, "active": true}}]
diff --git a/tools/pharos-dashboard/dashboard/fixtures/DBdata_test_bookings.json b/tools/pharos-dashboard/dashboard/fixtures/DBdata_test_bookings.json
deleted file mode 100644 (file)
index 1913281..0000000
+++ /dev/null
@@ -1 +0,0 @@
-[{"model": "dashboard.pod", "pk": 91, "fields": {"resource": 95, "chassis": null}}, {"model": "dashboard.pod", "pk": 92, "fields": {"resource": 96, "chassis": null}}, {"model": "dashboard.pod", "pk": 93, "fields": {"resource": 97, "chassis": null}}, {"model": "dashboard.pod", "pk": 94, "fields": {"resource": 98, "chassis": null}}, {"model": "dashboard.pod", "pk": 95, "fields": {"resource": 99, "chassis": null}}, {"model": "dashboard.pod", "pk": 96, "fields": {"resource": 100, "chassis": null}}, {"model": "dashboard.pod", "pk": 97, "fields": {"resource": 101, "chassis": null}}, {"model": "dashboard.pod", "pk": 98, "fields": {"resource": 102, "chassis": null}}, {"model": "dashboard.pod", "pk": 99, "fields": {"resource": 103, "chassis": null}}, {"model": "dashboard.pod", "pk": 100, "fields": {"resource": 104, "chassis": null}}, {"model": "dashboard.pod", "pk": 101, "fields": {"resource": 105, "chassis": null}}, {"model": "dashboard.pod", "pk": 102, "fields": {"resource": 106, "chassis": null}}, {"model": "dashboard.pod", "pk": 103, "fields": {"resource": 107, "chassis": null}}, {"model": "dashboard.pod", "pk": 104, "fields": {"resource": 108, "chassis": null}}, {"model": "dashboard.pod", "pk": 105, "fields": {"resource": 109, "chassis": null}}, {"model": "dashboard.pod", "pk": 106, "fields": {"resource": 110, "chassis": null}}, {"model": "dashboard.pod", "pk": 107, "fields": {"resource": 111, "chassis": null}}, {"model": "dashboard.pod", "pk": 108, "fields": {"resource": 112, "chassis": null}}, {"model": "dashboard.resource", "pk": 95, "fields": {"name": "Linux Foundation POD 1", "slavename": "lf-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 96, "fields": {"name": "Linux Foundation POD 2", "slavename": "lf-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 97, "fields": {"name": "Ericsson  POD 2", "slavename": "ericsson-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 98, "fields": {"name": "Intel POD 2", "slavename": "intel-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 99, "fields": {"name": "Intel POD 5", "slavename": "intel-pod5", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 100, "fields": {"name": "Intel POD 6", "slavename": "intel-pod6", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 101, "fields": {"name": "Intel POD 8", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 102, "fields": {"name": "Huawei POD 1", "slavename": "huawei-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 103, "fields": {"name": "Intel POD 3", "slavename": "intel-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 104, "fields": {"name": "Dell POD 1", "slavename": "dell-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 105, "fields": {"name": "Dell POD 2", "slavename": "dell-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 106, "fields": {"name": "Orange POD 2", "slavename": "orange-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 107, "fields": {"name": "Arm POD 1", "slavename": "arm-build1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 108, "fields": {"name": "Ericsson  POD 1", "slavename": "ericsson-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 109, "fields": {"name": "Huawei POD 2", "slavename": "huawei-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 110, "fields": {"name": "Huawei POD 3", "slavename": "huawei-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 111, "fields": {"name": "Huawei POD 4", "slavename": "huawei-pod4", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 112, "fields": {"name": "Intel POD 9", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9", "bookable": false, "active": true}}, {"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "dashboard", "model": "booking"}}, {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "dashboard", "model": "pod"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "dashboard", "model": "resource"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "dashboard", "model": "server"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "dashboard", "model": "userresource"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 9, "fields": {"app_label": "auth", "model": "user"}}, {"model": "contenttypes.contenttype", "pk": 10, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 11, "fields": {"app_label": "sessions", "model": "session"}}, {"model": "auth.permission", "pk": 1, "fields": {"name": "Can add booking", "content_type": 1, "codename": "add_booking"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change booking", "content_type": 1, "codename": "change_booking"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete booking", "content_type": 1, "codename": "delete_booking"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can add pod", "content_type": 2, "codename": "add_pod"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can change pod", "content_type": 2, "codename": "change_pod"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can delete pod", "content_type": 2, "codename": "delete_pod"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can add resource", "content_type": 3, "codename": "add_resource"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can change resource", "content_type": 3, "codename": "change_resource"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can delete resource", "content_type": 3, "codename": "delete_resource"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can add server", "content_type": 4, "codename": "add_server"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can change server", "content_type": 4, "codename": "change_server"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can delete server", "content_type": 4, "codename": "delete_server"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add user resource", "content_type": 5, "codename": "add_userresource"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change user resource", "content_type": 5, "codename": "change_userresource"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete user resource", "content_type": 5, "codename": "delete_userresource"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can add log entry", "content_type": 6, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can change log entry", "content_type": 6, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can delete log entry", "content_type": 6, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can add permission", "content_type": 7, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can change permission", "content_type": 7, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can delete permission", "content_type": 7, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can add group", "content_type": 8, "codename": "add_group"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can change group", "content_type": 8, "codename": "change_group"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can delete group", "content_type": 8, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add user", "content_type": 9, "codename": "add_user"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change user", "content_type": 9, "codename": "change_user"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete user", "content_type": 9, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can add content type", "content_type": 10, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can change content type", "content_type": 10, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can delete content type", "content_type": 10, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can add session", "content_type": 11, "codename": "add_session"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can change session", "content_type": 11, "codename": "change_session"}}, {"model": "auth.permission", "pk": 33, "fields": {"name": "Can delete session", "content_type": 11, "codename": "delete_session"}}, {"model": "auth.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$24000$kcQ5X8WQg1tH$KHynJPYTsoBq7vAipLsP0EZyo3qAu1VGJwaoEQeUz3E=", "last_login": "2016-07-24T13:01:31.199Z", "is_superuser": true, "username": "max", "first_name": "", "last_name": "", "email": "max.breitenfeldt@gmail.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:49:54.488Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 2, "fields": {"password": "pbkdf2_sha256$24000$6z3xKCpZp0xl$76IaKRzJocbGmGG9JUtPz3is2AjlMEIfb9omiTEn2N8=", "last_login": null, "is_superuser": true, "username": "jose", "first_name": "Jose", "last_name": "Lausuch", "email": "jose.lausuch@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:51:30Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 3, "fields": {"password": "pbkdf2_sha256$24000$466rSOMbdzZk$3TXRz9CRoephCrjNbEa7+nLJ4xwz4W0jvTfs4A69D1o=", "last_login": null, "is_superuser": true, "username": "daniel", "first_name": "Daniel", "last_name": "Smith", "email": "daniel.smith@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:52:23Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 4, "fields": {"password": "pbkdf2_sha256$24000$wjeFIjdiblRl$6ugfj1neDEsaxMQbQ0xV+zg/FKw8ImIDyuPqYWkUxE4=", "last_login": null, "is_superuser": true, "username": "jack", "first_name": "Jack", "last_name": "Morgan", "email": "jack.morgan@intel.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:53:02Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 5, "fields": {"password": "pbkdf2_sha256$24000$qYMcbKTahNGK$fdPgcQKTKhjSHDZx+C2bymU46HpIl/0n5vbXMpplXWE=", "last_login": null, "is_superuser": true, "username": "fatih", "first_name": "Fatih", "last_name": "Degirmenci", "email": "fatih.degirmenci@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:53:38Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 6, "fields": {"password": "pbkdf2_sha256$24000$Ndxyrota7u3U$KWmjRYo3GW25pcgf9NUuz5PSXvH8hU8E2dUARoKY7EI=", "last_login": null, "is_superuser": true, "username": "trevor", "first_name": "Trevor", "last_name": "Cooper", "email": "trevor.cooper@intel.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:54:32Z", "groups": [], "user_permissions": []}}, {"model": "admin.logentry", "pk": 1, "fields": {"action_time": "2016-07-24T12:51:30.147Z", "user": 1, "content_type": 9, "object_id": "2", "object_repr": "jose", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 2, "fields": {"action_time": "2016-07-24T12:51:58.928Z", "user": 1, "content_type": 9, "object_id": "2", "object_repr": "jose", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 3, "fields": {"action_time": "2016-07-24T12:52:23.821Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 4, "fields": {"action_time": "2016-07-24T12:52:40.446Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 5, "fields": {"action_time": "2016-07-24T12:52:45.866Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 6, "fields": {"action_time": "2016-07-24T12:53:02.303Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 7, "fields": {"action_time": "2016-07-24T12:53:22.399Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 8, "fields": {"action_time": "2016-07-24T12:53:24.541Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 9, "fields": {"action_time": "2016-07-24T12:53:38.910Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 10, "fields": {"action_time": "2016-07-24T12:53:54.585Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 11, "fields": {"action_time": "2016-07-24T12:53:56.777Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 12, "fields": {"action_time": "2016-07-24T12:54:32.683Z", "user": 1, "content_type": 9, "object_id": "6", "object_repr": "trevor", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 13, "fields": {"action_time": "2016-07-24T12:54:53.188Z", "user": 1, "content_type": 9, "object_id": "6", "object_repr": "trevor", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 14, "fields": {"action_time": "2016-07-24T12:55:45.789Z", "user": 1, "content_type": 5, "object_id": "1", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 15, "fields": {"action_time": "2016-07-24T12:55:51.347Z", "user": 1, "content_type": 5, "object_id": "2", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 16, "fields": {"action_time": "2016-07-24T12:55:56.704Z", "user": 1, "content_type": 5, "object_id": "3", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 17, "fields": {"action_time": "2016-07-24T12:56:08.238Z", "user": 1, "content_type": 5, "object_id": "4", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 18, "fields": {"action_time": "2016-07-24T12:56:17.849Z", "user": 1, "content_type": 5, "object_id": "5", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 19, "fields": {"action_time": "2016-07-24T12:56:24.215Z", "user": 1, "content_type": 5, "object_id": "6", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 20, "fields": {"action_time": "2016-07-24T12:56:33.608Z", "user": 1, "content_type": 5, "object_id": "7", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 21, "fields": {"action_time": "2016-07-24T12:56:37.554Z", "user": 1, "content_type": 5, "object_id": "8", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "dashboard.userresource", "pk": 1, "fields": {"user": 3, "resource": 108}}, {"model": "dashboard.userresource", "pk": 2, "fields": {"user": 3, "resource": 97}}, {"model": "dashboard.userresource", "pk": 3, "fields": {"user": 4, "resource": 98}}, {"model": "dashboard.userresource", "pk": 4, "fields": {"user": 4, "resource": 103}}, {"model": "dashboard.userresource", "pk": 5, "fields": {"user": 4, "resource": 99}}, {"model": "dashboard.userresource", "pk": 6, "fields": {"user": 4, "resource": 100}}, {"model": "dashboard.userresource", "pk": 7, "fields": {"user": 4, "resource": 101}}, {"model": "dashboard.userresource", "pk": 8, "fields": {"user": 4, "resource": 112}}, {"model": "dashboard.booking", "pk": 1, "fields": {"resource": 103, "user": 1, "start_date_time": "2016-07-23T00:00:00Z", "end_date_time": "2016-08-08T00:00:00Z", "creation": "2016-07-24T13:01:47.945Z", "purpose": "Test1"}}, {"model": "dashboard.booking", "pk": 2, "fields": {"resource": 103, "user": 1, "start_date_time": "2016-07-18T03:00:00Z", "end_date_time": "2016-07-20T10:00:00Z", "creation": "2016-07-24T13:02:00.033Z", "purpose": "test2"}}, {"model": "dashboard.booking", "pk": 3, "fields": {"resource": 109, "user": 1, "start_date_time": "2016-07-23T06:00:00Z", "end_date_time": "2016-07-26T17:00:00Z", "creation": "2016-07-24T13:02:23.024Z", "purpose": "test3"}}, {"model": "dashboard.booking", "pk": 4, "fields": {"resource": 110, "user": 1, "start_date_time": "2016-07-09T00:00:00Z", "end_date_time": "2016-08-01T00:00:00Z", "creation": "2016-07-24T13:02:35.138Z", "purpose": "test4"}}, {"model": "dashboard.booking", "pk": 5, "fields": {"resource": 111, "user": 1, "start_date_time": "2016-07-20T07:00:00Z", "end_date_time": "2016-07-20T10:00:00Z", "creation": "2016-07-24T13:02:45.153Z", "purpose": "test5"}}, {"model": "dashboard.booking", "pk": 6, "fields": {"resource": 111, "user": 1, "start_date_time": "2016-07-22T03:00:00Z", "end_date_time": "2016-07-24T10:00:00Z", "creation": "2016-07-24T13:02:50.050Z", "purpose": "test6"}}, {"model": "dashboard.booking", "pk": 7, "fields": {"resource": 111, "user": 1, "start_date_time": "2016-07-24T11:00:00Z", "end_date_time": "2016-07-24T19:00:00Z", "creation": "2016-07-24T13:02:57.207Z", "purpose": "test7"}}]
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/fixtures/DBdata_users.json b/tools/pharos-dashboard/dashboard/fixtures/DBdata_users.json
deleted file mode 100644 (file)
index d21be1f..0000000
+++ /dev/null
@@ -1 +0,0 @@
-[{"model": "dashboard.pod", "pk": 91, "fields": {"resource": 95, "chassis": null}}, {"model": "dashboard.pod", "pk": 92, "fields": {"resource": 96, "chassis": null}}, {"model": "dashboard.pod", "pk": 93, "fields": {"resource": 97, "chassis": null}}, {"model": "dashboard.pod", "pk": 94, "fields": {"resource": 98, "chassis": null}}, {"model": "dashboard.pod", "pk": 95, "fields": {"resource": 99, "chassis": null}}, {"model": "dashboard.pod", "pk": 96, "fields": {"resource": 100, "chassis": null}}, {"model": "dashboard.pod", "pk": 97, "fields": {"resource": 101, "chassis": null}}, {"model": "dashboard.pod", "pk": 98, "fields": {"resource": 102, "chassis": null}}, {"model": "dashboard.pod", "pk": 99, "fields": {"resource": 103, "chassis": null}}, {"model": "dashboard.pod", "pk": 100, "fields": {"resource": 104, "chassis": null}}, {"model": "dashboard.pod", "pk": 101, "fields": {"resource": 105, "chassis": null}}, {"model": "dashboard.pod", "pk": 102, "fields": {"resource": 106, "chassis": null}}, {"model": "dashboard.pod", "pk": 103, "fields": {"resource": 107, "chassis": null}}, {"model": "dashboard.pod", "pk": 104, "fields": {"resource": 108, "chassis": null}}, {"model": "dashboard.pod", "pk": 105, "fields": {"resource": 109, "chassis": null}}, {"model": "dashboard.pod", "pk": 106, "fields": {"resource": 110, "chassis": null}}, {"model": "dashboard.pod", "pk": 107, "fields": {"resource": 111, "chassis": null}}, {"model": "dashboard.pod", "pk": 108, "fields": {"resource": 112, "chassis": null}}, {"model": "dashboard.resource", "pk": 95, "fields": {"name": "Linux Foundation POD 1", "slavename": "lf-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 96, "fields": {"name": "Linux Foundation POD 2", "slavename": "lf-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 97, "fields": {"name": "Ericsson  POD 2", "slavename": "ericsson-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 98, "fields": {"name": "Intel POD 2", "slavename": "intel-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 99, "fields": {"name": "Intel POD 5", "slavename": "intel-pod5", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 100, "fields": {"name": "Intel POD 6", "slavename": "intel-pod6", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 101, "fields": {"name": "Intel POD 8", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 102, "fields": {"name": "Huawei POD 1", "slavename": "huawei-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 103, "fields": {"name": "Intel POD 3", "slavename": "intel-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 104, "fields": {"name": "Dell POD 1", "slavename": "dell-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 105, "fields": {"name": "Dell POD 2", "slavename": "dell-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 106, "fields": {"name": "Orange POD 2", "slavename": "orange-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 107, "fields": {"name": "Arm POD 1", "slavename": "arm-build1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 108, "fields": {"name": "Ericsson  POD 1", "slavename": "ericsson-pod1", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 109, "fields": {"name": "Huawei POD 2", "slavename": "huawei-pod2", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 110, "fields": {"name": "Huawei POD 3", "slavename": "huawei-pod3", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 111, "fields": {"name": "Huawei POD 4", "slavename": "huawei-pod4", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting", "bookable": false, "active": true}}, {"model": "dashboard.resource", "pk": 112, "fields": {"name": "Intel POD 9", "slavename": "intel-pod8", "description": "Some description", "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9", "bookable": false, "active": true}}, {"model": "contenttypes.contenttype", "pk": 1, "fields": {"app_label": "dashboard", "model": "booking"}}, {"model": "contenttypes.contenttype", "pk": 2, "fields": {"app_label": "dashboard", "model": "pod"}}, {"model": "contenttypes.contenttype", "pk": 3, "fields": {"app_label": "dashboard", "model": "resource"}}, {"model": "contenttypes.contenttype", "pk": 4, "fields": {"app_label": "dashboard", "model": "server"}}, {"model": "contenttypes.contenttype", "pk": 5, "fields": {"app_label": "dashboard", "model": "userresource"}}, {"model": "contenttypes.contenttype", "pk": 6, "fields": {"app_label": "admin", "model": "logentry"}}, {"model": "contenttypes.contenttype", "pk": 7, "fields": {"app_label": "auth", "model": "permission"}}, {"model": "contenttypes.contenttype", "pk": 8, "fields": {"app_label": "auth", "model": "group"}}, {"model": "contenttypes.contenttype", "pk": 9, "fields": {"app_label": "auth", "model": "user"}}, {"model": "contenttypes.contenttype", "pk": 10, "fields": {"app_label": "contenttypes", "model": "contenttype"}}, {"model": "contenttypes.contenttype", "pk": 11, "fields": {"app_label": "sessions", "model": "session"}}, {"model": "auth.permission", "pk": 1, "fields": {"name": "Can add booking", "content_type": 1, "codename": "add_booking"}}, {"model": "auth.permission", "pk": 2, "fields": {"name": "Can change booking", "content_type": 1, "codename": "change_booking"}}, {"model": "auth.permission", "pk": 3, "fields": {"name": "Can delete booking", "content_type": 1, "codename": "delete_booking"}}, {"model": "auth.permission", "pk": 4, "fields": {"name": "Can add pod", "content_type": 2, "codename": "add_pod"}}, {"model": "auth.permission", "pk": 5, "fields": {"name": "Can change pod", "content_type": 2, "codename": "change_pod"}}, {"model": "auth.permission", "pk": 6, "fields": {"name": "Can delete pod", "content_type": 2, "codename": "delete_pod"}}, {"model": "auth.permission", "pk": 7, "fields": {"name": "Can add resource", "content_type": 3, "codename": "add_resource"}}, {"model": "auth.permission", "pk": 8, "fields": {"name": "Can change resource", "content_type": 3, "codename": "change_resource"}}, {"model": "auth.permission", "pk": 9, "fields": {"name": "Can delete resource", "content_type": 3, "codename": "delete_resource"}}, {"model": "auth.permission", "pk": 10, "fields": {"name": "Can add server", "content_type": 4, "codename": "add_server"}}, {"model": "auth.permission", "pk": 11, "fields": {"name": "Can change server", "content_type": 4, "codename": "change_server"}}, {"model": "auth.permission", "pk": 12, "fields": {"name": "Can delete server", "content_type": 4, "codename": "delete_server"}}, {"model": "auth.permission", "pk": 13, "fields": {"name": "Can add user resource", "content_type": 5, "codename": "add_userresource"}}, {"model": "auth.permission", "pk": 14, "fields": {"name": "Can change user resource", "content_type": 5, "codename": "change_userresource"}}, {"model": "auth.permission", "pk": 15, "fields": {"name": "Can delete user resource", "content_type": 5, "codename": "delete_userresource"}}, {"model": "auth.permission", "pk": 16, "fields": {"name": "Can add log entry", "content_type": 6, "codename": "add_logentry"}}, {"model": "auth.permission", "pk": 17, "fields": {"name": "Can change log entry", "content_type": 6, "codename": "change_logentry"}}, {"model": "auth.permission", "pk": 18, "fields": {"name": "Can delete log entry", "content_type": 6, "codename": "delete_logentry"}}, {"model": "auth.permission", "pk": 19, "fields": {"name": "Can add permission", "content_type": 7, "codename": "add_permission"}}, {"model": "auth.permission", "pk": 20, "fields": {"name": "Can change permission", "content_type": 7, "codename": "change_permission"}}, {"model": "auth.permission", "pk": 21, "fields": {"name": "Can delete permission", "content_type": 7, "codename": "delete_permission"}}, {"model": "auth.permission", "pk": 22, "fields": {"name": "Can add group", "content_type": 8, "codename": "add_group"}}, {"model": "auth.permission", "pk": 23, "fields": {"name": "Can change group", "content_type": 8, "codename": "change_group"}}, {"model": "auth.permission", "pk": 24, "fields": {"name": "Can delete group", "content_type": 8, "codename": "delete_group"}}, {"model": "auth.permission", "pk": 25, "fields": {"name": "Can add user", "content_type": 9, "codename": "add_user"}}, {"model": "auth.permission", "pk": 26, "fields": {"name": "Can change user", "content_type": 9, "codename": "change_user"}}, {"model": "auth.permission", "pk": 27, "fields": {"name": "Can delete user", "content_type": 9, "codename": "delete_user"}}, {"model": "auth.permission", "pk": 28, "fields": {"name": "Can add content type", "content_type": 10, "codename": "add_contenttype"}}, {"model": "auth.permission", "pk": 29, "fields": {"name": "Can change content type", "content_type": 10, "codename": "change_contenttype"}}, {"model": "auth.permission", "pk": 30, "fields": {"name": "Can delete content type", "content_type": 10, "codename": "delete_contenttype"}}, {"model": "auth.permission", "pk": 31, "fields": {"name": "Can add session", "content_type": 11, "codename": "add_session"}}, {"model": "auth.permission", "pk": 32, "fields": {"name": "Can change session", "content_type": 11, "codename": "change_session"}}, {"model": "auth.permission", "pk": 33, "fields": {"name": "Can delete session", "content_type": 11, "codename": "delete_session"}}, {"model": "auth.user", "pk": 1, "fields": {"password": "pbkdf2_sha256$24000$kcQ5X8WQg1tH$KHynJPYTsoBq7vAipLsP0EZyo3qAu1VGJwaoEQeUz3E=", "last_login": "2016-07-24T12:50:11.954Z", "is_superuser": true, "username": "max", "first_name": "", "last_name": "", "email": "max.breitenfeldt@gmail.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:49:54.488Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 2, "fields": {"password": "pbkdf2_sha256$24000$6z3xKCpZp0xl$76IaKRzJocbGmGG9JUtPz3is2AjlMEIfb9omiTEn2N8=", "last_login": null, "is_superuser": true, "username": "jose", "first_name": "Jose", "last_name": "Lausuch", "email": "jose.lausuch@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:51:30Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 3, "fields": {"password": "pbkdf2_sha256$24000$466rSOMbdzZk$3TXRz9CRoephCrjNbEa7+nLJ4xwz4W0jvTfs4A69D1o=", "last_login": null, "is_superuser": true, "username": "daniel", "first_name": "Daniel", "last_name": "Smith", "email": "daniel.smith@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:52:23Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 4, "fields": {"password": "pbkdf2_sha256$24000$wjeFIjdiblRl$6ugfj1neDEsaxMQbQ0xV+zg/FKw8ImIDyuPqYWkUxE4=", "last_login": null, "is_superuser": true, "username": "jack", "first_name": "Jack", "last_name": "Morgan", "email": "jack.morgan@intel.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:53:02Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 5, "fields": {"password": "pbkdf2_sha256$24000$qYMcbKTahNGK$fdPgcQKTKhjSHDZx+C2bymU46HpIl/0n5vbXMpplXWE=", "last_login": null, "is_superuser": true, "username": "fatih", "first_name": "Fatih", "last_name": "Degirmenci", "email": "fatih.degirmenci@ericsson.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:53:38Z", "groups": [], "user_permissions": []}}, {"model": "auth.user", "pk": 6, "fields": {"password": "pbkdf2_sha256$24000$Ndxyrota7u3U$KWmjRYo3GW25pcgf9NUuz5PSXvH8hU8E2dUARoKY7EI=", "last_login": null, "is_superuser": true, "username": "trevor", "first_name": "Trevor", "last_name": "Cooper", "email": "trevor.cooper@intel.com", "is_staff": true, "is_active": true, "date_joined": "2016-07-24T12:54:32Z", "groups": [], "user_permissions": []}}, {"model": "admin.logentry", "pk": 1, "fields": {"action_time": "2016-07-24T12:51:30.147Z", "user": 1, "content_type": 9, "object_id": "2", "object_repr": "jose", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 2, "fields": {"action_time": "2016-07-24T12:51:58.928Z", "user": 1, "content_type": 9, "object_id": "2", "object_repr": "jose", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 3, "fields": {"action_time": "2016-07-24T12:52:23.821Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 4, "fields": {"action_time": "2016-07-24T12:52:40.446Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 5, "fields": {"action_time": "2016-07-24T12:52:45.866Z", "user": 1, "content_type": 9, "object_id": "3", "object_repr": "daniel", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 6, "fields": {"action_time": "2016-07-24T12:53:02.303Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 7, "fields": {"action_time": "2016-07-24T12:53:22.399Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 8, "fields": {"action_time": "2016-07-24T12:53:24.541Z", "user": 1, "content_type": 9, "object_id": "4", "object_repr": "jack", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 9, "fields": {"action_time": "2016-07-24T12:53:38.910Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 10, "fields": {"action_time": "2016-07-24T12:53:54.585Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 11, "fields": {"action_time": "2016-07-24T12:53:56.777Z", "user": 1, "content_type": 9, "object_id": "5", "object_repr": "fatih", "action_flag": 2, "change_message": "No fields changed."}}, {"model": "admin.logentry", "pk": 12, "fields": {"action_time": "2016-07-24T12:54:32.683Z", "user": 1, "content_type": 9, "object_id": "6", "object_repr": "trevor", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 13, "fields": {"action_time": "2016-07-24T12:54:53.188Z", "user": 1, "content_type": 9, "object_id": "6", "object_repr": "trevor", "action_flag": 2, "change_message": "Changed first_name, last_name, email, is_staff and is_superuser."}}, {"model": "admin.logentry", "pk": 14, "fields": {"action_time": "2016-07-24T12:55:45.789Z", "user": 1, "content_type": 5, "object_id": "1", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 15, "fields": {"action_time": "2016-07-24T12:55:51.347Z", "user": 1, "content_type": 5, "object_id": "2", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 16, "fields": {"action_time": "2016-07-24T12:55:56.704Z", "user": 1, "content_type": 5, "object_id": "3", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 17, "fields": {"action_time": "2016-07-24T12:56:08.238Z", "user": 1, "content_type": 5, "object_id": "4", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 18, "fields": {"action_time": "2016-07-24T12:56:17.849Z", "user": 1, "content_type": 5, "object_id": "5", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 19, "fields": {"action_time": "2016-07-24T12:56:24.215Z", "user": 1, "content_type": 5, "object_id": "6", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 20, "fields": {"action_time": "2016-07-24T12:56:33.608Z", "user": 1, "content_type": 5, "object_id": "7", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "admin.logentry", "pk": 21, "fields": {"action_time": "2016-07-24T12:56:37.554Z", "user": 1, "content_type": 5, "object_id": "8", "object_repr": "UserResource object", "action_flag": 1, "change_message": "Added."}}, {"model": "dashboard.userresource", "pk": 1, "fields": {"user": 3, "resource": 108}}, {"model": "dashboard.userresource", "pk": 2, "fields": {"user": 3, "resource": 97}}, {"model": "dashboard.userresource", "pk": 3, "fields": {"user": 4, "resource": 98}}, {"model": "dashboard.userresource", "pk": 4, "fields": {"user": 4, "resource": 103}}, {"model": "dashboard.userresource", "pk": 5, "fields": {"user": 4, "resource": 99}}, {"model": "dashboard.userresource", "pk": 6, "fields": {"user": 4, "resource": 100}}, {"model": "dashboard.userresource", "pk": 7, "fields": {"user": 4, "resource": 101}}, {"model": "dashboard.userresource", "pk": 8, "fields": {"user": 4, "resource": 112}}]
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/fixtures/dashboard.json b/tools/pharos-dashboard/dashboard/fixtures/dashboard.json
new file mode 100644 (file)
index 0000000..f8c1fc1
--- /dev/null
@@ -0,0 +1,218 @@
+[
+{
+    "model": "dashboard.resource",
+    "pk": 1,
+    "fields": {
+        "name": "Linux Foundation POD 1",
+        "slavename": "lf-pod1",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab",
+        "bookable": false,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 2,
+    "fields": {
+        "name": "Linux Foundation POD 2",
+        "slavename": "lf-pod2",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab",
+        "bookable": false,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 3,
+    "fields": {
+        "name": "Ericsson  POD 2",
+        "slavename": "ericsson-pod2",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process",
+        "bookable": false,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 4,
+    "fields": {
+        "name": "Intel POD 2",
+        "slavename": "intel-pod2",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2",
+        "bookable": false,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 5,
+    "fields": {
+        "name": "Intel POD 5",
+        "slavename": "intel-pod5",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5",
+        "bookable": false,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 6,
+    "fields": {
+        "name": "Intel POD 6",
+        "slavename": "intel-pod6",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6",
+        "bookable": false,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 7,
+    "fields": {
+        "name": "Intel POD 8",
+        "slavename": "intel-pod8",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8",
+        "bookable": false,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 8,
+    "fields": {
+        "name": "Huawei POD 1",
+        "slavename": "huawei-pod1",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting",
+        "bookable": false,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 9,
+    "fields": {
+        "name": "Intel POD 3",
+        "slavename": "intel-pod3",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3",
+        "bookable": true,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 10,
+    "fields": {
+        "name": "Dell POD 1",
+        "slavename": "dell-pod1",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting",
+        "bookable": true,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 11,
+    "fields": {
+        "name": "Dell POD 2",
+        "slavename": "dell-pod2",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting",
+        "bookable": true,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 12,
+    "fields": {
+        "name": "Orange POD 2",
+        "slavename": "orange-pod2",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2",
+        "bookable": true,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 13,
+    "fields": {
+        "name": "Arm POD 1",
+        "slavename": "arm-build1",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab",
+        "bookable": true,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 14,
+    "fields": {
+        "name": "Ericsson POD 1",
+        "slavename": "ericsson-pod1",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Ericsson+Hosting+and+Request+Process",
+        "bookable": true,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 15,
+    "fields": {
+        "name": "Huawei POD 2",
+        "slavename": "huawei-pod2",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting",
+        "bookable": true,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 16,
+    "fields": {
+        "name": "Huawei POD 3",
+        "slavename": "huawei-pod3",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting",
+        "bookable": true,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 17,
+    "fields": {
+        "name": "Huawei POD 4",
+        "slavename": "huawei-pod4",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting",
+        "bookable": true,
+        "active": true
+    }
+},
+{
+    "model": "dashboard.resource",
+    "pk": 18,
+    "fields": {
+        "name": "Intel POD 9",
+        "slavename": "intel-pod9",
+        "description": "Some description",
+        "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9",
+        "bookable": true,
+        "active": true
+    }
+}
+]
diff --git a/tools/pharos-dashboard/dashboard/forms/booking_form.py b/tools/pharos-dashboard/dashboard/forms/booking_form.py
deleted file mode 100644 (file)
index 9cf8048..0000000
+++ /dev/null
@@ -1,37 +0,0 @@
-from dashboard.models import Booking
-import django.forms as forms
-from django.utils.translation import ugettext_lazy as _
-
-
-class BookingForm(forms.ModelForm):
-    class Meta:
-        model = Booking
-        fields = ['start_date_time', 'end_date_time', 'purpose', 'booking_id']
-
-        PURPOSE = {
-            'id': 'purposefield',
-            'type': 'text',
-            'placeholder': 'Booking purpose',
-        }
-
-        widgets = {
-            'purpose': forms.TextInput(attrs=PURPOSE),
-        }
-
-    # DATETIMEFORMAT should be equivalent to the moment.js format string that datetimepicker is
-    # using ('YYYY-MM-DD HH:00 ZZ'). The string is used to create a timezone aware datetime object
-    DATETIMEFORMAT = '%Y-%m-%d %H:%M %z'
-    start_date_time = forms.DateTimeField(input_formats=[DATETIMEFORMAT, ], label='Start')
-    end_date_time = forms.DateTimeField(input_formats=[DATETIMEFORMAT, ], label='End')
-
-    # we need this to determine if we create a new booking or change an existing booking
-    booking_id = forms.IntegerField(widget=forms.HiddenInput, required=False)
-
-    def clean(self):
-        cleaned_data = super(BookingForm, self).clean()
-        if 'start_date_time' not in cleaned_data or 'end_date_time' not in cleaned_data:
-            raise forms.ValidationError('Date Missing', code='missing_date')
-        if cleaned_data['start_date_time'] >= cleaned_data['end_date_time']:
-            raise forms.ValidationError(
-                'Start date is after end date', code='invalid_dates')
-        return cleaned_data
diff --git a/tools/pharos-dashboard/dashboard/jenkins/jenkins_util.py b/tools/pharos-dashboard/dashboard/jenkins/jenkins_util.py
deleted file mode 100644 (file)
index ba94563..0000000
+++ /dev/null
@@ -1,70 +0,0 @@
-import dashboard.jenkins.jenkins_adapter as jenkins
-import re
-
-
-def parse_slave_data(slave_dict, slave):
-    slave_dict['status'] = get_slave_status(slave)
-    slave_dict['status_color'] = get_status_color(slave)
-    slave_dict['slaveurl'] = get_slave_url(slave)
-    job = jenkins.get_jenkins_job(slave['displayName'])
-    if job is not None:
-        slave_dict['last_job'] = parse_job(job)
-
-
-def parse_job(job):
-    result = parse_job_string(job['lastBuild']['fullDisplayName'])
-    result['url'] = job['url']
-    result['color'] = get_job_color(job)
-    if job['lastBuild']['building']:
-        result['blink'] = 'class=blink_me'
-    return result
-
-
-def parse_job_string(full_displayname):
-    job = {}
-    tokens = re.split(r'[ -]', full_displayname)
-    for i in range(len(tokens)):
-        if tokens[i] == 'os':
-            job['scenario'] = '-'.join(tokens[i: i + 4])
-        elif tokens[i] in ['fuel', 'joid', 'apex', 'compass']:
-            job['installer'] = tokens[i]
-        elif tokens[i] in ['master', 'arno', 'brahmaputra', 'colorado']:
-            job['branch'] = tokens[i]
-
-    tokens = full_displayname.split(' ')
-    job['name'] = tokens[0]
-    return job
-
-
-# TODO: use css
-def get_job_color(job):
-    if job['lastBuild']['building'] is True:
-        return '#646F73'
-    result = job['lastBuild']['result']
-    if result == 'SUCCESS':
-        return '#33cc00'
-    if result == 'FAILURE':
-        return '#FF5555'
-    if result == 'UNSTABLE':
-        return '#EDD62B'
-
-
-# TODO: use css
-def get_status_color(slave):
-    if not slave['offline'] and slave['idle']:
-        return '#C8D6C3'
-    if not slave['offline']:
-        return '#BEFAAA'
-    return '#FAAAAB'
-
-
-def get_slave_url(slave):
-    return 'https://build.opnfv.org/ci/computer/' + slave['displayName']
-
-
-def get_slave_status(slave):
-    if not slave['offline'] and slave['idle']:
-        return 'online / idle'
-    if not slave['offline']:
-        return 'online'
-    return 'offline'
index 12de299..b93d829 100644 (file)
@@ -1,10 +1,8 @@
 # -*- coding: utf-8 -*-
-# Generated by Django 1.9.8 on 2016-07-24 13:06
+# Generated by Django 1.10 on 2016-08-12 09:51
 from __future__ import unicode_literals
 
-from django.conf import settings
 from django.db import migrations, models
-import django.db.models.deletion
 
 
 class Migration(migrations.Migration):
@@ -12,42 +10,16 @@ class Migration(migrations.Migration):
     initial = True
 
     dependencies = [
-        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
     ]
 
     operations = [
-        migrations.CreateModel(
-            name='Booking',
-            fields=[
-                ('booking_id', models.AutoField(primary_key=True, serialize=False)),
-                ('start_date_time', models.DateTimeField()),
-                ('end_date_time', models.DateTimeField()),
-                ('creation', models.DateTimeField(auto_now=True)),
-                ('purpose', models.CharField(max_length=300)),
-            ],
-            options={
-                'db_table': 'booking',
-            },
-        ),
-        migrations.CreateModel(
-            name='Pod',
-            fields=[
-                ('pod_id', models.AutoField(primary_key=True, serialize=False)),
-                ('chassis', models.CharField(blank=True, max_length=500, null=True)),
-            ],
-            options={
-                'db_table': 'pod',
-            },
-        ),
         migrations.CreateModel(
             name='Resource',
             fields=[
-                ('resource_id', models.AutoField(
-                    primary_key=True, serialize=False)),
+                ('id', models.AutoField(primary_key=True, serialize=False)),
                 ('name', models.CharField(max_length=100, unique=True)),
                 ('slavename', models.CharField(blank=True, max_length=50, null=True)),
-                ('description', models.CharField(
-                    blank=True, max_length=300, null=True)),
+                ('description', models.CharField(blank=True, max_length=300, null=True)),
                 ('url', models.CharField(blank=True, max_length=100, null=True)),
                 ('bookable', models.BooleanField(default=False)),
                 ('active', models.BooleanField(default=True)),
@@ -56,52 +28,4 @@ class Migration(migrations.Migration):
                 'db_table': 'resource',
             },
         ),
-        migrations.CreateModel(
-            name='Server',
-            fields=[
-                ('server_id', models.AutoField(primary_key=True, serialize=False)),
-                ('model', models.CharField(blank=True, max_length=200, null=True)),
-                ('cpu', models.CharField(blank=True, max_length=200, null=True)),
-                ('ram', models.CharField(blank=True, max_length=200, null=True)),
-                ('storage', models.CharField(blank=True, max_length=200, null=True)),
-                ('count', models.IntegerField(default=1)),
-                ('resource', models.ForeignKey(
-                    on_delete=django.db.models.deletion.DO_NOTHING, to='dashboard.Resource')),
-            ],
-            options={
-                'db_table': 'server',
-            },
-        ),
-        migrations.CreateModel(
-            name='UserResource',
-            fields=[
-                ('user_resource_id', models.AutoField(
-                    primary_key=True, serialize=False)),
-                ('resource', models.ForeignKey(
-                    on_delete=django.db.models.deletion.CASCADE, to='dashboard.Resource')),
-                ('user', models.ForeignKey(
-                    on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
-            ],
-            options={
-                'db_table': 'user_resource',
-            },
-        ),
-        migrations.AddField(
-            model_name='pod',
-            name='resource',
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE, to='dashboard.Resource'),
-        ),
-        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='user',
-            field=models.ForeignKey(
-                on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL),
-        ),
     ]
diff --git a/tools/pharos-dashboard/dashboard/migrations/0002_resourceutilization.py b/tools/pharos-dashboard/dashboard/migrations/0002_resourceutilization.py
new file mode 100644 (file)
index 0000000..8e10acf
--- /dev/null
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-13 12:50
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dashboard', '0001_initial'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='ResourceUtilization',
+            fields=[
+                ('timestamp', models.DateTimeField(auto_created=True)),
+                ('id', models.AutoField(primary_key=True, serialize=False)),
+                ('pod_status', models.IntegerField()),
+            ],
+        ),
+    ]
diff --git a/tools/pharos-dashboard/dashboard/migrations/0003_auto_20160813_1302.py b/tools/pharos-dashboard/dashboard/migrations/0003_auto_20160813_1302.py
new file mode 100644 (file)
index 0000000..8c4c213
--- /dev/null
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-13 13:02
+from __future__ import unicode_literals
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('dashboard', '0002_resourceutilization'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='resource',
+            name='active',
+        ),
+        migrations.RemoveField(
+            model_name='resource',
+            name='bookable',
+        ),
+    ]
diff --git a/tools/pharos-dashboard/dashboard/migrations/0004_resource_owners.py b/tools/pharos-dashboard/dashboard/migrations/0004_resource_owners.py
new file mode 100644 (file)
index 0000000..b851588
--- /dev/null
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+# Generated by Django 1.10 on 2016-08-13 14:43
+from __future__ import unicode_literals
+
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('dashboard', '0003_auto_20160813_1302'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='resource',
+            name='owners',
+            field=models.ManyToManyField(to=settings.AUTH_USER_MODEL),
+        ),
+    ]
index 132ba7c..973066b 100644 (file)
@@ -1,63 +1,14 @@
-from __future__ import unicode_literals
-
-from django.db import models
 from django.contrib.auth.models import User
-
-
-class Booking(models.Model):
-    booking_id = models.AutoField(primary_key=True)
-    # Bookings should be deleted before resources
-    resource = models.ForeignKey('Resource', models.PROTECT)
-    # delete Booking when user is deleted
-    user = models.ForeignKey(User, models.CASCADE)
-    start_date_time = models.DateTimeField()
-    end_date_time = models.DateTimeField()
-    creation = models.DateTimeField(auto_now=True)
-    purpose = models.CharField(max_length=300)
-
-    class Meta:
-        db_table = 'booking'
-
-    # check for conflicting bookings before saving
-    def save(self, *args, **kwargs):
-        # conflicts end after booking starts, and start before booking ends
-        conflicting_bookings = Booking.objects.filter(resource_id=self.resource_id)
-        conflicting_bookings = conflicting_bookings.filter(end_date_time__gt=self.start_date_time)
-        conflicting_bookings = conflicting_bookings.filter(start_date_time__lt=self.end_date_time)
-        # we may change a booking, so it is not a conflict
-        conflicting_bookings = conflicting_bookings.exclude(booking_id=self.booking_id)
-        if conflicting_bookings.count() > 0:
-            raise ValueError('This booking overlaps with another booking')
-        super(Booking, self).save(*args, **kwargs)
-
-
-def __str__(self):
-    return 'Booking: ' + str(self.resource)
-
-
-class Pod(models.Model):
-    pod_id = models.AutoField(primary_key=True)
-    # Delete Pod with resource
-    resource = models.ForeignKey('Resource', models.CASCADE)
-    chassis = models.CharField(max_length=500, blank=True, null=True)
-
-    class Meta:
-        db_table = 'pod'
-
-    def __str__(self):
-        if self.chassis is None:
-            return str(self.pod_id) + ' ' + str(self.resource)
-        return str(self.pod_id) + ' ' + self.chassis
+from django.db import models
 
 
 class Resource(models.Model):
-    resource_id = models.AutoField(primary_key=True)
+    id = models.AutoField(primary_key=True)
     name = models.CharField(max_length=100, unique=True)
     slavename = models.CharField(max_length=50, blank=True, null=True)
     description = models.CharField(max_length=300, blank=True, null=True)
     url = models.CharField(max_length=100, blank=True, null=True)
-    bookable = models.BooleanField(default=False)
-    active = models.BooleanField(default=True)
+    owners = models.ManyToManyField(User)
 
     class Meta:
         db_table = 'resource'
@@ -65,25 +16,13 @@ class Resource(models.Model):
     def __str__(self):
         return self.name
 
+class ResourceUtilization(models.Model):
+    POD_STATUS = {
+        'online': 1,
+        'idle': 2,
+        'offline': 3
+    }
 
-class Server(models.Model):
-    server_id = models.AutoField(primary_key=True)
-    resource = models.ForeignKey(Resource, models.DO_NOTHING)
-    model = models.CharField(max_length=200, blank=True, null=True)
-    cpu = models.CharField(max_length=200, blank=True, null=True)
-    ram = models.CharField(max_length=200, blank=True, null=True)
-    storage = models.CharField(max_length=200, blank=True, null=True)
-    count = models.IntegerField(default=1)
-
-    class Meta:
-        db_table = 'server'
-
-
-class UserResource(models.Model):
-    user_resource_id = models.AutoField(primary_key=True)
-    user = models.ForeignKey(User, models.CASCADE)
-    # Delete if Resource is deleted
-    resource = models.ForeignKey(Resource, models.CASCADE)
-
-    class Meta:
-        db_table = 'user_resource'
+    id = models.AutoField(primary_key=True)
+    timestamp = models.DateTimeField(auto_created=True)
+    pod_status = models.IntegerField()
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/static/js/booking-calendar.js b/tools/pharos-dashboard/dashboard/static/js/booking-calendar.js
deleted file mode 100644 (file)
index edc2055..0000000
+++ /dev/null
@@ -1,68 +0,0 @@
-function parseDisabledTimeIntervals(bookings) {
-    var timeIntervals = [];
-
-    for (var i = 0; i < bookings.length; i++) {
-        var interval = [
-            moment(bookings[i]['start_date_time']),
-            moment(bookings[i]['end_date_time'])
-        ];
-        timeIntervals.push(interval);
-    }
-    return timeIntervals;
-}
-
-function parseCalendarEvents(bookings) {
-    var events = [];
-    for (var i = 0; i < bookings.length; i++) {
-        event = {
-            id: bookings[i]['booking_id'],
-            title: bookings[i]['purpose'],
-            start: bookings[i]['start_date_time'],
-            end: bookings[i]['end_date_time'],
-            editable: true
-        };
-        events.push(event);
-    }
-    return events;
-}
-
-function loadEvents(bookings_url) {
-    $.ajax({
-        url: bookings_url,
-        type: 'get',
-        success: function (data) {
-            $('#calendar').fullCalendar('addEventSource', parseCalendarEvents(data['bookings']));
-            var intervals = parseDisabledTimeIntervals(data['bookings']);
-            $('#starttimepicker').data("DateTimePicker").disabledTimeIntervals(intervals);
-            $('#endtimepicker').data("DateTimePicker").disabledTimeIntervals(intervals);
-        },
-        failure: function (data) {
-            alert('Error loading booking data');
-        }
-    });
-}
-
-$(document).ready(function () {
-    $('#calendar').fullCalendar(calendarOptions);
-    $('#starttimepicker').datetimepicker(timepickerOptions);
-    $('#endtimepicker').datetimepicker(timepickerOptions);
-
-    loadEvents(bookings_url);
-
-    // send Post request to delete url if button is clicked
-    $("#deletebutton").click(function () {
-        var booking_id = $('#id_booking_id').val();
-        $.ajax({
-            type: 'post',
-            url: '/booking/' + booking_id + '/delete',
-            success: function () {
-                $('#calendar').fullCalendar('removeEvents');
-                loadEvents(bookings_url);
-                $('#calendar').fullCalendar('rerenderEvents');
-            },
-            failure: function () {
-                alert('Deleting failed')
-            }
-        })
-    })
-});
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/static/js/csrf.js b/tools/pharos-dashboard/dashboard/static/js/csrf.js
deleted file mode 100644 (file)
index 12429b3..0000000
+++ /dev/null
@@ -1,34 +0,0 @@
-/**
- * use django csrf token in ajax requests
- * source: https://docs.djangoproject.com/en/1.8/ref/csrf/#ajax
- */
-// using jQuery
-function getCookie(name) {
-    var cookieValue = null;
-    if (document.cookie && document.cookie != '') {
-        var cookies = document.cookie.split(';');
-        for (var i = 0; i < cookies.length; i++) {
-            var cookie = jQuery.trim(cookies[i]);
-            // Does this cookie string begin with the name we want?
-            if (cookie.substring(0, name.length + 1) == (name + '=')) {
-                cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
-                break;
-            }
-        }
-    }
-    return cookieValue;
-}
-var csrftoken = getCookie('csrftoken');
-
-function csrfSafeMethod(method) {
-    // these HTTP methods do not require CSRF protection
-    return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
-}
-
-$.ajaxSetup({
-    beforeSend: function (xhr, settings) {
-        if (!csrfSafeMethod(settings.type) && !this.crossDomain) {
-            xhr.setRequestHeader("X-CSRFToken", csrftoken);
-        }
-    }
-});
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/static/js/datetimepicker-options.js b/tools/pharos-dashboard/dashboard/static/js/datetimepicker-options.js
deleted file mode 100644 (file)
index 1deb819..0000000
+++ /dev/null
@@ -1,7 +0,0 @@
-/**
- * Created by max on 7/25/16.
- */
-var timepickerOptions = {
-    format: 'YYYY-MM-DD HH:00 ZZ',
-    sideBySide: true
-};
\ No newline at end of file
diff --git a/tools/pharos-dashboard/dashboard/static/js/fullcalendar-options.js b/tools/pharos-dashboard/dashboard/static/js/fullcalendar-options.js
deleted file mode 100644 (file)
index c0417eb..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-// converts a moment to a readable fomat for the backend
-function convertInputTime(time) {
-    return moment(time).format('YYYY-MM-DD HH:00 ZZ');
-}
-
-function sendEventToForm(event) {
-    var start = convertInputTime(event.start);
-    var end = convertInputTime(event.end);
-    $('#starttimepicker').data("DateTimePicker").date(start);
-    $('#endtimepicker').data("DateTimePicker").date(end);
-    $('#submitform').html("Change Booking");
-    $('#purposefield').val(event.title);
-    $('#id_booking_id').val(event.id); // create a new booking
-    $("#deletebutton").removeClass('hidden'); // show delete button
-}
-
-var calendarOptions = {
-    height: 600,
-    header: {
-        left: 'prev,next today',
-        center: 'title',
-        right: 'agendaWeek,month'
-    },
-    timezone: 'local',
-    defaultView: 'agendaWeek',
-    slotDuration: '00:60:00',
-    slotLabelFormat: "HH:mm",
-    firstDay: 1,
-    allDaySlot: false,
-    selectOverlap: false,
-    eventOverlap: false,
-    selectable: true,
-    selectHelper: true,
-    editable: false,
-    eventLimit: true, // allow "more" link when too many events
-    timeFormat: 'H(:mm)', // uppercase H for 24-hour clock
-    unselectAuto: false,
-
-    select: function (start, end) {
-        var start = convertInputTime(start);
-        var end = convertInputTime(end);
-
-        $('#starttimepicker').data("DateTimePicker").date(start);
-        $('#endtimepicker').data("DateTimePicker").date(end);
-        $('#submitform').html("Book Pod");
-        $('#purposefield').val('');
-        $('#id_booking_id').val(''); // create a new booking
-        $("#deletebutton").addClass('hidden'); // hide delete button
-    },
-
-    eventClick: function (event, jsEvent, view) {
-        $('#calendar').fullCalendar('unselect');
-        sendEventToForm(event);
-    },
-
-    eventDrop: function (event) {
-        $('#calendar').fullCalendar('unselect');
-        sendEventToForm(event);
-    },
-
-    eventResize: function (event) {
-        $('#calendar').fullCalendar('unselect');
-        sendEventToForm(event);
-    }
-};
\ No newline at end of file
index 8050eb8..cf6340f 100644 (file)
@@ -1,35 +1,26 @@
-from dashboard.views.booking import BookingCalendarView, ResourceBookingsView, DeleteBookingView
-from dashboard.views.table_views import CIPodsView, DevelopmentPodsView, JenkinsSlavesView
-from django.conf.urls import url, include
-from django.contrib.auth import views as auth_views
+"""pharos_dashboard URL Configuration
 
+The `urlpatterns` list routes URLs to views. For more information please see:
+    https://docs.djangoproject.com/en/1.10/topics/http/urls/
+Examples:
+Function views
+    1. Add an import:  from my_app import views
+    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
+Class-based views
+    1. Add an import:  from other_app.views import Home
+    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
+Including another URLconf
+    1. Import the include() function: from django.conf.urls import url, include
+    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
+"""
+from django.conf.urls import url
 
-urlpatterns = [
-    # registration
-    url(r'^accounts/login/$', auth_views.login, name='login'),
-    url(r'^accounts/logout/$', auth_views.logout, name='logout'),
-
-    # Index
-    url(r'^index/$', CIPodsView.as_view(), name='index'),
-    url(r'^index/$', CIPodsView.as_view(), name='index'),
-    url(r'^$', CIPodsView.as_view(), name=""),
+from dashboard.views import *
 
-    # Tables
+urlpatterns = [
     url(r'^ci_pods/$', CIPodsView.as_view(), name='ci_pods'),
     url(r'^dev_pods/$', DevelopmentPodsView.as_view(), name='dev_pods'),
     url(r'^jenkins_slaves/$', JenkinsSlavesView.as_view(), name='jenkins_slaves'),
 
-    # Booking Calendar
-    url(r'^booking_calendar/$', DevelopmentPodsView.as_view(),
-        name='booking_calendar'),
-    url(r'^booking_calendar/(?P<resource_id>[0-9]+)/$',
-        BookingCalendarView.as_view(), name='booking_calendar'),
-    url(r'^booking_calendar/(?P<resource_id>[0-9]+)/(?P<booking_id>[0-9]+)/$',
-        BookingCalendarView.as_view(), name='booking_calendar'),
-
-    # AJAX urls
-    url(r'^resource/(?P<resource_id>[0-9]+)/bookings/$',
-        ResourceBookingsView.as_view(), name='resource_bookings'),
-    url(r'^booking/(?P<booking_id>[0-9]+)/delete$',
-        DeleteBookingView.as_view(), name='delete_booking'),
+    url(r'^$', DevelopmentPodsView.as_view(), name="index"),
 ]
@@ -1,18 +1,18 @@
-import dashboard.jenkins.jenkins_util as jenkins_util
-
-import dashboard.jenkins.jenkins_adapter as jenkins
-from dashboard.models import Resource, Booking
 from django.utils import timezone
 from django.views.generic import TemplateView
 
+from booking.models import Booking
+from dashboard.models import Resource
+from jenkins import adapter as jenkins
+
 
 class JenkinsSlavesView(TemplateView):
-    template_name = "tables/jenkins_slaves.html"
+    template_name = "dashboard/jenkins_slaves.html"
 
     def get_context_data(self, **kwargs):
         slaves = jenkins.get_all_slaves()
         for slave in slaves:
-            jenkins_util.parse_slave_data(slave, slave)
+            jenkins.parse_slave_data(slave, slave)
 
         context = super(JenkinsSlavesView, self).get_context_data(**kwargs)
         context.update({'title': "Jenkins Slaves", 'slaves': slaves})
@@ -20,7 +20,7 @@ class JenkinsSlavesView(TemplateView):
 
 
 class CIPodsView(TemplateView):
-    template_name = "tables/ci_pods.html"
+    template_name = "dashboard/ci_pods.html"
 
     def get_context_data(self, **kwargs):
         resources = Resource.objects.filter().values()  # get resources as a set of dicts
@@ -29,7 +29,7 @@ class CIPodsView(TemplateView):
             if not jenkins.is_ci_slave(resource['slavename']):
                 continue
             ci_slave = jenkins.get_slave(resource['slavename'])
-            jenkins_util.parse_slave_data(resource, ci_slave)
+            jenkins.parse_slave_data(resource, ci_slave)
             ci_pods.append(resource)
 
         context = super(CIPodsView, self).get_context_data(**kwargs)
@@ -38,20 +38,20 @@ class CIPodsView(TemplateView):
 
 
 class DevelopmentPodsView(TemplateView):
-    template_name = "tables/dev_pods.html"
+    template_name = "dashboard/dev_pods.html"
 
     def get_context_data(self, **kwargs):
         resources = Resource.objects.filter().values()  # get resources as a set of dicts
         dev_pods = []
 
-        current_bookings = Booking.objects.filter(start_date_time__lte=timezone.now())
-        current_bookings = current_bookings.filter(end_date_time__gt=timezone.now())
+        current_bookings = Booking.objects.filter(start__lte=timezone.now())
+        current_bookings = current_bookings.filter(end__gt=timezone.now())
 
         for resource in resources:
             if not jenkins.is_dev_pod(resource['slavename']):
                 continue
             dev_pod = jenkins.get_slave(resource['slavename'])
-            jenkins_util.parse_slave_data(resource, dev_pod)
+            jenkins.parse_slave_data(resource, dev_pod)
             for booking in current_bookings:
                 if booking.resource.slavename == resource['slavename']:
                     resource['current_booking'] = booking
diff --git a/tools/pharos-dashboard/dashboard/views/booking.py b/tools/pharos-dashboard/dashboard/views/booking.py
deleted file mode 100644 (file)
index 1499edb..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-from dashboard.forms.booking_form import BookingForm
-from dashboard.models import Resource, Booking
-from dashboard.views.registration import BookingUserTestMixin
-from django.contrib import messages
-from django.contrib.auth.decorators import login_required
-from django.http import Http404, JsonResponse
-from django.shortcuts import get_object_or_404
-from django.utils.decorators import method_decorator
-from django.views.generic import FormView, View
-
-
-@method_decorator(login_required, name='dispatch')
-class BookingCalendarView(BookingUserTestMixin, FormView):
-    template_name = "dashboard/booking_calendar.html"
-    form_class = BookingForm
-
-    # set instance variables
-    def dispatch(self, request, *args, **kwargs):
-        self.foo = request.GET.get('foo', False)
-        self.resource = get_object_or_404(Resource, resource_id=self.kwargs['resource_id'])
-        return super(BookingCalendarView, self).dispatch(request, *args, **kwargs)
-
-    def form_valid(self, form):
-        self.success_url = self.request.path
-        booking = None
-        # change existing booking
-        if form.cleaned_data['booking_id'] is not None:
-            booking = get_object_or_404(Booking, booking_id=form.cleaned_data['booking_id'])
-        # create new booking
-        else:
-            booking = Booking()
-            booking.resource = self.resource
-            booking.user = self.request.user
-        booking.start_date_time = form.cleaned_data['start_date_time']
-        booking.end_date_time = form.cleaned_data['end_date_time']
-        booking.purpose = form.cleaned_data['purpose']
-        try:
-            booking.save()
-        except ValueError:
-            messages.add_message(self.request, messages.ERROR,
-                                 'This booking overlaps with another booking')
-            return super(BookingCalendarView, self).form_invalid(form)
-        messages.add_message(self.request, messages.SUCCESS,
-                             'Booking saved')
-        return super(BookingCalendarView, self).form_valid(form)
-
-    def get_context_data(self, **kwargs):
-        title = 'Booking: ' + self.resource.name
-        context = super(BookingCalendarView, self).get_context_data(**kwargs)
-        context.update({'title': title, 'resource': self.resource})
-        return context
-
-
-@method_decorator(login_required, name='dispatch')
-class ResourceBookingsView(BookingUserTestMixin, View):
-    def get(self, request, *args, **kwargs):
-        resource = Resource.objects.get(resource_id=self.kwargs['resource_id'])
-        bookings = resource.booking_set.get_queryset().values(
-            'booking_id', 'user__username', 'user__email',
-            'start_date_time', 'end_date_time', 'purpose')
-        return JsonResponse({'bookings': list(bookings)})
-
-
-@method_decorator(login_required, name='dispatch')
-class DeleteBookingView(BookingUserTestMixin, View):
-    def post(self, request, *args, **kwargs):
-        booking = get_object_or_404(Booking, booking_id=self.kwargs['booking_id'])
-        booking.delete()
-        return JsonResponse({True: self.kwargs['booking_id']})
diff --git a/tools/pharos-dashboard/dashboard/views/registration.py b/tools/pharos-dashboard/dashboard/views/registration.py
deleted file mode 100644 (file)
index 516fb29..0000000
+++ /dev/null
@@ -1,16 +0,0 @@
-from django.contrib.auth.mixins import UserPassesTestMixin
-
-
-class BookingUserTestMixin(UserPassesTestMixin):
-    # Test if a user has permission to book this Pod
-    def test_func(self):
-        user = self.request.user
-        # Check if User is troubleshooter / admin
-        if user.has_perm(('dashboard.add_booking')):
-            return True
-        # Check if User owns this resource
-        user_resources = user.userresource_set.get_queryset()
-        for user_resource in user_resources:
-            if user_resource.resource_id == self.resource.resource_id:
-                return True
-        return False
diff --git a/tools/pharos-dashboard/issues.org b/tools/pharos-dashboard/issues.org
deleted file mode 100644 (file)
index 0a7d3ca..0000000
+++ /dev/null
@@ -1,6 +0,0 @@
-* Fullcalendar
-- no selectHelper for month select
-- if an event is selected in month select, browser timezone is ignored
-
-* layout
-- datatable does not stay in its panel if zoom is to high / browser window is not maximized
diff --git a/tools/pharos-dashboard/jenkins/__init__.py b/tools/pharos-dashboard/jenkins/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
@@ -1,5 +1,7 @@
-import requests
 import logging
+
+import re
+import requests
 from django.core.cache import cache
 
 logger = logging.getLogger(__name__)
@@ -82,3 +84,70 @@ def is_dev_pod(slavename):
     if slavename.find('pod') != -1:
         return True
     return False
+
+def parse_slave_data(slave_dict, slave):
+    slave_dict['status'] = get_slave_status(slave)
+    slave_dict['status_color'] = get_status_color(slave)
+    slave_dict['slaveurl'] = get_slave_url(slave)
+    job = get_jenkins_job(slave['displayName'])
+    if job is not None:
+        slave_dict['last_job'] = parse_job(job)
+
+
+def parse_job(job):
+    result = parse_job_string(job['lastBuild']['fullDisplayName'])
+    result['url'] = job['url']
+    result['color'] = get_job_color(job)
+    if job['lastBuild']['building']:
+        result['blink'] = 'class=blink_me'
+    return result
+
+
+def parse_job_string(full_displayname):
+    job = {}
+    tokens = re.split(r'[ -]', full_displayname)
+    for i in range(len(tokens)):
+        if tokens[i] == 'os':
+            job['scenario'] = '-'.join(tokens[i: i + 4])
+        elif tokens[i] in ['fuel', 'joid', 'apex', 'compass']:
+            job['installer'] = tokens[i]
+        elif tokens[i] in ['master', 'arno', 'brahmaputra', 'colorado']:
+            job['branch'] = tokens[i]
+
+    tokens = full_displayname.split(' ')
+    job['name'] = tokens[0]
+    return job
+
+
+# TODO: use css
+def get_job_color(job):
+    if job['lastBuild']['building'] is True:
+        return '#646F73'
+    result = job['lastBuild']['result']
+    if result == 'SUCCESS':
+        return '#5cb85c'
+    if result == 'FAILURE':
+        return '#d9534f'
+    if result == 'UNSTABLE':
+        return '#EDD62B'
+
+
+# TODO: use css
+def get_status_color(slave):
+    if not slave['offline'] and slave['idle']:
+        return '#5bc0de'
+    if not slave['offline']:
+        return '#5cb85c'
+    return '#d9534f'
+
+
+def get_slave_url(slave):
+    return 'https://build.opnfv.org/ci/computer/' + slave['displayName']
+
+
+def get_slave_status(slave):
+    if not slave['offline'] and slave['idle']:
+        return 'online / idle'
+    if not slave['offline']:
+        return 'online'
+    return 'offline'
diff --git a/tools/pharos-dashboard/jenkins/apps.py b/tools/pharos-dashboard/jenkins/apps.py
new file mode 100644 (file)
index 0000000..5abd215
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class JenkinsConfig(AppConfig):
+    name = 'jenkins'
similarity index 91%
rename from tools/pharos-dashboard/dashboard/tests.py
rename to tools/pharos-dashboard/jenkins/tests.py
index 7509578..b141451 100644 (file)
@@ -1,5 +1,6 @@
-import dashboard.jenkins.jenkins_adapter as jenkins
-from django.test import SimpleTestCase
+from unittest import TestCase
+
+import jenkins.adapter as jenkins
 
 
 # Tests that the data we get with the jenkinsadapter contains all the
@@ -8,7 +9,7 @@ from django.test import SimpleTestCase
 # - the opnfv jenkins url has changed
 # - the jenkins api has changed
 # - jenkins is not set up / there is no data
-class JenkinsAdapterTestCase(SimpleTestCase):
+class JenkinsAdapterTestCase(TestCase):
     def test_get_all_slaves(self):
         slaves = jenkins.get_all_slaves()
         self.assertTrue(len(slaves) > 0)
index 65e6fc6..97a5ba4 100755 (executable)
@@ -4,7 +4,19 @@ import sys
 
 if __name__ == "__main__":
     os.environ.setdefault("DJANGO_SETTINGS_MODULE", "pharos_dashboard.settings")
-
-    from django.core.management import execute_from_command_line
-
+    try:
+        from django.core.management import execute_from_command_line
+    except ImportError:
+        # The above import may fail for some other reason. Ensure that the
+        # issue is really that Django is missing to avoid masking other
+        # exceptions on Python 2.
+        try:
+            import django
+        except ImportError:
+            raise ImportError(
+                "Couldn't import Django. Are you sure it's installed and "
+                "available on your PYTHONPATH environment variable? Did you "
+                "forget to activate a virtual environment?"
+            )
+        raise
     execute_from_command_line(sys.argv)
index 2bc9496..b6e9899 100644 (file)
@@ -1,13 +1,13 @@
 """
-Django settings for opnfvdashboard project.
+Django settings for pharos_dashboard project.
 
-Generated by 'django-admin startproject' using Django 1.9.7.
+Generated by 'django-admin startproject' using Django 1.10.
 
 For more information on this file, see
-https://docs.djangoproject.com/en/1.9/topics/settings/
+https://docs.djangoproject.com/en/1.10/topics/settings/
 
 For the full list of settings and their values, see
-https://docs.djangoproject.com/en/1.9/ref/settings/
+https://docs.djangoproject.com/en/1.10/ref/settings/
 """
 
 import os
@@ -15,39 +15,45 @@ import os
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
+
 # Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
+# See https://docs.djangoproject.com/en/1.10/howto/deployment/checklist/
 
 # SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = '=awtgkzaq@ytwbsp$$n=7=m&9*cm7gci7o-dy07)!x1um=g(gf'
+SECRET_KEY = 'j*mq-lt&^*9e$%4mtc)f2$_i(wky#g@ycl3@&$^5n89a3^i)7i'
 
 # SECURITY WARNING: don't run with debug turned on in production!
 DEBUG = True
 
 ALLOWED_HOSTS = []
 
+
 # Application definition
 
 INSTALLED_APPS = [
     'dashboard',
+    'booking',
+    'account',
+    'jenkins',
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
-    'bootstrap3'
+    'django.contrib.humanize',
+    'bootstrap3',
 ]
 
-MIDDLEWARE_CLASSES = [
+MIDDLEWARE = [
     'django.middleware.security.SecurityMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',
     'django.middleware.common.CommonMiddleware',
     'django.middleware.csrf.CsrfViewMiddleware',
     'django.contrib.auth.middleware.AuthenticationMiddleware',
-    'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
     'django.contrib.messages.middleware.MessageMiddleware',
     'django.middleware.clickjacking.XFrameOptionsMiddleware',
+    'account.middleware.TimezoneMiddleware',
 ]
 
 ROOT_URLCONF = 'pharos_dashboard.urls'
@@ -71,8 +77,9 @@ TEMPLATES = [
 
 WSGI_APPLICATION = 'pharos_dashboard.wsgi.application'
 
+
 # Database
-# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
+# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
 
 DATABASES = {
     'default': {
@@ -85,8 +92,9 @@ DATABASES = {
     }
 }
 
+
 # Password validation
-# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
+# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
 
 AUTH_PASSWORD_VALIDATORS = [
     {
@@ -103,10 +111,8 @@ AUTH_PASSWORD_VALIDATORS = [
     },
 ]
 
-LOGIN_REDIRECT_URL = '/'
-
 # Internationalization
-# https://docs.djangoproject.com/en/1.9/topics/i18n/
+# https://docs.djangoproject.com/en/1.10/topics/i18n/
 
 LANGUAGE_CODE = 'en-us'
 
@@ -118,7 +124,18 @@ USE_L10N = True
 
 USE_TZ = True
 
+
 # Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/1.9/howto/static-files/
+# https://docs.djangoproject.com/en/1.10/howto/static-files/
 
 STATIC_URL = '/static/'
+
+STATICFILES_DIRS = [
+    os.path.join(BASE_DIR, "static"),
+]
+
+BOOTSTRAP3 = {
+    'set_placeholder': False,
+}
+
+LOGIN_REDIRECT_URL = '/'
index 03b9c25..41aa409 100644 (file)
@@ -1,7 +1,7 @@
-"""opnfvdashboard URL Configuration
+"""pharos_dashboard URL Configuration
 
 The `urlpatterns` list routes URLs to views. For more information please see:
-    https://docs.djangoproject.com/en/1.9/topics/http/urls/
+    https://docs.djangoproject.com/en/1.10/topics/http/urls/
 Examples:
 Function views
     1. Add an import:  from my_app import views
@@ -13,11 +13,12 @@ Including another URLconf
     1. Import the include() function: from django.conf.urls import url, include
     2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
 """
-
-from django.conf.urls import include, url
+from django.conf.urls import url, include
 from django.contrib import admin
 
 urlpatterns = [
     url(r'^', include('dashboard.urls', namespace='dashboard')),
-    url(r'^admin/', include(admin.site.urls)),
+    url(r'^booking/', include('booking.urls', namespace='booking')),
+    url(r'^account/', include('account.urls', namespace='account')),
+    url(r'^admin/', admin.site.urls),
 ]
\ No newline at end of file
index 54f5735..b127751 100644 (file)
@@ -4,7 +4,7 @@ WSGI config for pharos_dashboard project.
 It exposes the WSGI callable as a module-level variable named ``application``.
 
 For more information on this file, see
-https://docs.djangoproject.com/en/1.9/howto/deployment/wsgi/
+https://docs.djangoproject.com/en/1.10/howto/deployment/wsgi/
 """
 
 import os
diff --git a/tools/pharos-dashboard/requirements.txt b/tools/pharos-dashboard/requirements.txt
deleted file mode 100644 (file)
index 6306c0b..0000000
+++ /dev/null
@@ -1,5 +0,0 @@
-Django==1.9.8
-django-bootstrap3==7.0.1
-psycopg2==2.6.2
-pytz==2016.6.1
-requests==2.10.0
diff --git a/tools/pharos-dashboard/static/js/booking-calendar.js b/tools/pharos-dashboard/static/js/booking-calendar.js
new file mode 100644 (file)
index 0000000..f8a9a0f
--- /dev/null
@@ -0,0 +1,36 @@
+function parseCalendarEvents(bookings) {
+    var events = [];
+    for (var i = 0; i < bookings.length; i++) {
+        // convert ISO 8601 timestring to moment, needed for timezone handling
+        start = moment(bookings[i]['start']);
+        end = moment(bookings[i]['end']);
+        event = {
+            id: bookings[i]['id'],
+            title: bookings[i]['purpose'],
+            start: start,
+            end: end,
+        };
+        events.push(event);
+    }
+    return events;
+}
+
+function loadEvents(url) {
+    $.ajax({
+        url: url,
+        type: 'get',
+        success: function (data) {
+            $('#calendar').fullCalendar('addEventSource', parseCalendarEvents(data['bookings']));
+        },
+        failure: function (data) {
+            alert('Error loading booking data');
+        }
+    });
+}
+
+$(document).ready(function () {
+    $('#calendar').fullCalendar(calendarOptions);
+    loadEvents(bookings_url);
+    $('#starttimepicker').datetimepicker(timepickerOptions);
+    $('#endtimepicker').datetimepicker(timepickerOptions);
+});
\ No newline at end of file
diff --git a/tools/pharos-dashboard/static/js/datetimepicker-options.js b/tools/pharos-dashboard/static/js/datetimepicker-options.js
new file mode 100644 (file)
index 0000000..2d19dda
--- /dev/null
@@ -0,0 +1,3 @@
+var timepickerOptions = {
+    format: 'MM/DD/YYYY HH:00'
+};
\ No newline at end of file
diff --git a/tools/pharos-dashboard/static/js/fullcalendar-options.js b/tools/pharos-dashboard/static/js/fullcalendar-options.js
new file mode 100644 (file)
index 0000000..85423b8
--- /dev/null
@@ -0,0 +1,78 @@
+var tmpevent;
+
+// converts a moment to a readable fomat for the backend
+function convertInputTime(time) {
+    return time;
+    //return moment(time).format('YYYY-MM-DD HH:00 ZZ');
+}
+
+function sendEventToForm(event) {
+    $('#starttimepicker').data("DateTimePicker").date(convertInputTime(event.start));
+    $('#endtimepicker').data("DateTimePicker").date(convertInputTime(event.end));
+}
+
+var calendarOptions = {
+    height: 600,
+    header: {
+        left: 'prev,next today',
+        center: 'title',
+        right: 'agendaWeek,month'
+    },
+    timezone: user_timezone, // set in booking_calendar.html
+    defaultView: 'month',
+    slotDuration: '00:60:00',
+    slotLabelFormat: "HH:mm",
+    firstDay: 1,
+    allDaySlot: false,
+    selectOverlap: false,
+    eventOverlap: false,
+    selectable: true,
+    editable: false,
+    eventLimit: true, // allow "more" link when too many events
+    timeFormat: 'H(:mm)', // uppercase H for 24-hour clock
+    unselectAuto: true,
+    nowIndicator: true,
+
+    // selectHelper is only working in the agendaWeek view, this is a workaround:
+    // if an event is selected, the existing selection is removed and a temporary event is added
+    // to the calendar
+    select: function (start, end) {
+        if (tmpevent != undefined) {
+            $('#calendar').fullCalendar('removeEvents', tmpevent.id);
+            $('#calendar').fullCalendar('rerenderEvents');
+        }
+        // the times need to be converted here to make them show up in the agendaWeek view if they
+        // are created in the month view. If they are not converted, the tmpevent will only show
+        // up in the (deactivated) allDaySlot
+        start = convertInputTime(start);
+        end = convertInputTime(end);
+
+        tmpevent = {
+            id: '537818f62bc63518ece15338fb86c8be',
+            title: 'New Booking',
+            start: start,
+            end: end,
+            editable: true
+        };
+
+        $('#calendar').fullCalendar('renderEvent', tmpevent, true);
+        sendEventToForm(tmpevent);
+    },
+
+    eventClick: function (event) {
+        if (tmpevent != undefined) {
+            if (event.id != tmpevent.id) {
+                $('#calendar').fullCalendar('removeEvents', tmpevent.id);
+                $('#calendar').fullCalendar('rerenderEvents');
+            }
+        }
+    },
+
+    eventDrop: function (event) {
+        sendEventToForm(event);
+    },
+
+    eventResize: function (event) {
+        sendEventToForm(event);
+    }
+};
\ No newline at end of file
@@ -1,4 +1,4 @@
-{% extends "layout/base.html" %}
+{% extends "layout.html" %}
 {% load bootstrap3 %}
 
 {% block basecontent %}
@@ -16,7 +16,7 @@
                 </button>
                 <a href="https://www.opnfv.org/" class="navbar-left"><img
                         src="https://www.opnfv.org/sites/all/themes/opnfv/logo.png"></a>
-                <a class="navbar-brand" href={% url 'dashboard:' %}>Pharos Dashboard</a>
+                <a class="navbar-brand" href={% url 'dashboard:index' %}>Pharos Dashboard</a>
             </div>
             <!-- /.navbar-header -->
 
                     </a>
                     <ul class="dropdown-menu dropdown-user">
                         {% if user.is_authenticated %}
-                            <li><a href="#"><i class="fa fa-user fa-fw"></i> User Profile</a>
-                            </li>
-                            <li><a href="#"><i class="fa fa-gear fa-fw"></i> Settings</a>
+                            <li><a href="{% url 'account:settings' %}"><i
+                                    class="fa fa-gear fa-fw"></i>
+                                Settings</a>
                             </li>
                             <li class="divider"></li>
-                            <li><a href="{% url 'dashboard:logout' %}?next={{ request.path }}"><i
+                            <li><a href="{% url 'account:logout' %}?next={{ request.path }}"><i
                                     class="fa fa-sign-out fa-fw"></i>
                                 Logout</a>
                             </li>
                         {% else %}
-                            <li><a href="{% url 'dashboard:login' %}"><i
-                                class="fa fa-sign-out fa-fw"></i>
-                            Login</a>
+                            <li><a href="{% url 'account:login' %}"><i
+                                    class="fa fa-sign-in fa-fw"></i>
+                                Login</a>
+                            <li>
+                            <a href="{% url 'account:registration' %}"><i
+                                    class="fa fa-edit fa-fw"></i>
+                                Register</a>
                         {% endif %}
                     </ul>
                     <!-- /.dropdown-user -->
diff --git a/tools/pharos-dashboard/templates/booking/booking_calendar.html b/tools/pharos-dashboard/templates/booking/booking_calendar.html
new file mode 100644 (file)
index 0000000..1fa5dc4
--- /dev/null
@@ -0,0 +1,56 @@
+{% extends "dashboard/table.html" %}
+{% load staticfiles %}
+
+{% block extrahead %}
+    <link href="{% static "bower_components/fullcalendar/dist/fullcalendar.css" %}"
+          rel='stylesheet'/>
+    <link href="{% static "bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css" %}"
+          rel='stylesheet'/>
+{% endblock extrahead %}
+
+{% block content %}
+    <div class="row">
+    <div class="col-lg-8">
+        <div class="container-fluid">
+            <div class="panel panel-default">
+                <div class="panel-heading">
+                    <i class="fa fa-calendar fa-fw"></i>Calendar
+                </div>
+                <div class="panel-body">
+                    <div id='calendar'>
+                    </div>
+                </div>
+                <!-- /.panel-body -->
+            </div>
+            <!-- /.panel -->
+        </div>
+    </div>
+
+    <div class="col-lg-4">
+        <div class="panel panel-default">
+            <div class="panel-heading">
+                <i class="fa fa-edit fa-fw"></i>Booking
+            </div>
+            <div class="panel-body">
+                <div id="booking_form_div">
+                    {% include 'booking/booking_form.html' %}
+                </div>
+            </div>
+        </div>
+    </div>
+{% endblock content %}
+
+{% block extrajs %}
+    <script type="text/javascript">
+        var bookings_url = "{% url 'booking:bookings_json' resource_id=resource.id %}"
+        var user_timezone = "{{ request.user.userprofile.timezone }}"
+    </script>
+
+    <script src={% static "bower_components/moment/moment.js" %}></script>
+    <script src={% static "bower_components/fullcalendar/dist/fullcalendar.js" %}></script>
+    <script type="text/javascript"
+            src={% static "bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js" %}></script>
+    <script src={% static "js/fullcalendar-options.js" %}></script>
+    <script src={% static "js/datetimepicker-options.js" %}></script>
+    <script src={% static "js/booking-calendar.js" %}></script>
+{% endblock extrajs %}
\ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/booking/booking_form.html b/tools/pharos-dashboard/templates/booking/booking_form.html
new file mode 100644 (file)
index 0000000..f13c4b4
--- /dev/null
@@ -0,0 +1,21 @@
+{% load bootstrap3 %}
+
+
+{% 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>
\ No newline at end of file
diff --git a/tools/pharos-dashboard/templates/dashboard/booking_calendar.html b/tools/pharos-dashboard/templates/dashboard/booking_calendar.html
deleted file mode 100644 (file)
index 0f6bece..0000000
+++ /dev/null
@@ -1,79 +0,0 @@
-{% extends "dashboard/base.html" %}
-{% load staticfiles %}
-{% load bootstrap3 %}
-
-{% block extrahead %}
-    <link href="{% static "bower_components/fullcalendar/dist/fullcalendar.css" %}"
-          rel='stylesheet'/>
-    <link href="{% static "bower_components/eonasdan-bootstrap-datetimepicker/build/css/bootstrap-datetimepicker.min.css" %}"
-          rel='stylesheet'/>
-{% endblock extrahead %}
-
-{% block content %}
-    <div class="row">
-        <div class="col-lg-8">
-            <div class="container-fluid">
-                <div class="panel panel-default">
-                    <div class="panel-heading">
-                        Calendar
-                    </div>
-                    <div class="panel-body">
-                        <div id='calendar'>
-                        </div>
-                    </div>
-                    <!-- /.panel-body -->
-                </div>
-                <!-- /.panel -->
-            </div>
-        </div>
-
-        <div class="col-lg-4">
-            <div class="panel panel-default">
-                <div class="panel-heading">
-                    Booking
-                </div>
-                <div class="panel-body">
-                    {% bootstrap_form_errors form type='non_fields' %}
-
-                    <form method="post" class="form" id="bookingform">
-                        {% csrf_token %}
-                        <div class='input-group' id='starttimepicker'>
-                            {% bootstrap_field form.start_date_time addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
-                        </div>
-                        <div class='input-group' id='endtimepicker'>
-                            {% bootstrap_field form.end_date_time addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
-                        </div>
-                        {% bootstrap_field form.purpose %}
-                        {{ form.booking_id }}
-                        {% buttons %}
-                            <button type="submit" class="btn btn btn-success"
-                                    id="submitform">
-                                Book Pod
-                            </button>
-                            <button type="button" class="btn btn btn-danger hidden"
-                                    id="deletebutton">
-                                Delete Booking
-                            </button>
-                        {% endbuttons %}
-                    </form>
-
-                </div>
-            </div>
-        </div>
-    </div>
-{% endblock content %}
-
-{% block extrajs %}
-    <script type="text/javascript">
-        var bookings_url = '/resource/' + {{ resource.resource_id }} +'/bookings/';
-    </script>
-
-    <script src={% static "bower_components/moment/moment.js" %}></script>
-    <script src={% static "bower_components/fullcalendar/dist/fullcalendar.js" %}></script>
-    <script type="text/javascript"
-            src={% static "bower_components/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js" %}></script>
-    <script src={% static "js/fullcalendar-options.js" %}></script>
-    <script src={% static "js/datetimepicker-options.js" %}></script>
-    <script src={% static "js/csrf.js" %}></script>
-    <script src={% static "js/booking-calendar.js" %}></script>
-{% endblock extrajs %}
\ No newline at end of file
                 {{ pod.last_job.branch }}
             </th>
             <th><a {{ pod.last_job.blink }} style="color:{{ pod.last_job.color }}"
-                                           target='_blank'
-                                           href={{ pod.last_job.url }}>{{ pod.last_job.name }}</a>
+                                            target='_blank'
+                                            href={{ pod.last_job.url }}>{{ pod.last_job.name }}</a>
             </th>
         </tr>
     {% endfor %}`
     </tbody>
-    </table>
 {% endblock table %}
 
 
     </tr>
     </thead>
     <tbody>
-    {% for pod in dev_pods %}
+    {% for resource in dev_pods %}
         <tr>
             <th>
-                <a target='_blank' href={{ pod.url }}>{{ pod.name }}</a>
+                <a target='_blank' href={{ resource.url }}>{{ resource.name }}</a>
             </th>
             <th>
-                <a target='_blank' href={{ pod.slaveurl }}>{{ pod.slavename }}</a>
+                <a target='_blank' href={{ resource.slaveurl }}>{{ resource.slavename }}</a>
             </th>
             <th>
-                {{ pod.current_booking.user }}
+                {{ resource.current_booking.user.username }}
             </th>
             <th>
-                {{ pod.current_booking.end_date_time }}
+                {{ resource.current_booking.end }}
             </th>
             <th>
-                {{ pod.current_booking.purpose }}
+                {{ resource.current_booking.purpose }}
             </th>
-            <th style="background-color:{{ pod.status_color }}">
-                {{ pod.status }}
+            <th style="background-color:{{ resource.status_color }}">
+                {{ resource.status }}
             </th>
             <th>
-                <a href='{% url 'dashboard:booking_calendar' %}{{ pod.resource_id }}' class="btn btn-primary">
+                <a href="{% url 'booking:create' resource_id=resource.id %}" class="btn btn-primary">
                     Book
                 </a>
             </th>
index 2d0b82e..addd5c1 100644 (file)
@@ -1,4 +1,4 @@
-{% extends "dashboard/base.html" %}
+{% extends "base.html" %}
 {% load staticfiles %}
 
 {% block extrahead %}
 {% block basecontent %}
 {% endblock basecontent %}
 
-<!-- jQuery -->
-<script src="{% static "bower_components/jquery/dist/jquery.min.js" %}"></script>
-<script src="{% static "bower_components/jquery-migrate/jquery-migrate.min.js" %}"></script>
+
+<script src="https://code.jquery.com/jquery-2.2.4.min.js"
+        integrity="sha256-BbhdlvQf/xTY9gja0Dq3HiwQF8LaCRTXxZKRutelT44=" crossorigin="anonymous"></script>
+{#<!-- jQuery -->#}
+{#<script src="{% static "bower_components/jquery/dist/jquery.min.js" %}"></script>#}
+{#<script src="{% static "bower_components/jquery-migrate/jquery-migrate.min.js" %}"></script>#}
 
 {#<script src="https://code.jquery.com/jquery-2.2.0.min.js"></script>#}
 <!-- Bootstrap Core JavaScript -->
index efdcd1f..45b9d45 100644 (file)
@@ -1,4 +1,5 @@
-{% extends "layout/base.html" %}
+{% extends "layout.html" %}
+{% load bootstrap3 %}
 
 {% block basecontent %}
     <div class="container">
                         {% endif %}
                     </div>
                 {% endif %}
-                {% if form.errors %}
-                    <div class="alert alert-danger alert-dismissable">
-                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
-                            <span aria-hidden="true">&times;</span>
-                        </button>
-                        Your username and password didn't match. Please try again.
-                    </div>
-                {% endif %}
             </div>
         </div>
         <div class="row">
                         </h3>
                     </div>
                     <div class="panel-body">
-                        <form method="post" action="{% url 'dashboard:login' %}">
+                        <form method="post">
                             {% csrf_token %}
-                            <fieldset>
-                                <div class="form-group">
-                                    <input class="form-control" placeholder="Username" name="username" type="text"
-                                           autofocus>
-                                </div>
-                                <div class="form-group">
-                                    <input class="form-control" placeholder="Password" name="password"
-                                           type="password" value="">
-                                </div>
-                                <input type="submit" value="Login" class="btn btn-lg btn-success btn-block"/>
-                                <input type="hidden" name="next" value="{{ next }}"/>
-                            </fieldset>
+                            {% bootstrap_form form %}
+                            {% buttons %}
+                                <button type="submit" class="btn btn btn-success">
+                                    Login
+                                </button>
+                                <a href="{% url 'account:registration' %}" class="btn btn-info"
+                                   role="button">Register</a>
+                            {% endbuttons %}
                         </form>
                     </div>
                 </div>
             </div>
         </div>
     </div>
+
     {# Assumes you setup the password_reset view in your URLconf #}
     {# <p><a href="{% url 'password_reset' %}">Lost password?</a></p>#}
 {% endblock basecontent %}
diff --git a/tools/pharos-dashboard/templates/registration/registration_form.html b/tools/pharos-dashboard/templates/registration/registration_form.html
new file mode 100644 (file)
index 0000000..0a921d5
--- /dev/null
@@ -0,0 +1,30 @@
+{% extends "layout.html" %}
+{% load bootstrap3 %}
+
+{% block basecontent %}
+    <div class="container">
+        <div class="row">
+            <div class="col-md-4 col-md-offset-4">
+                {% bootstrap_messages %}
+                <div class="login-panel panel panel-default">
+                    <div class="panel-heading">
+                        <h3 class="panel-title">
+                            {{ title }}
+                        </h3>
+                    </div>
+                    <div class="panel-body">
+                        <form method="post" action="">
+                            {% csrf_token %}
+                            {% bootstrap_form form %}
+                            {% buttons %}
+                                <button type="submit" class="btn btn btn-success">
+                                    Submit
+                                </button>
+                            {% endbuttons %}
+                        </form>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+{% endblock basecontent %}