import os
import oauth2 as oauth
+from django.conf import settings
from jira import JIRA
from tlslite.utils import keyfactory
-from django.conf import settings
-
class SignatureMethod_RSA_SHA1(oauth.SignatureMethod):
name = 'RSA-SHA1'
##############################################################################
-from django.db import models
-
from django.contrib.auth.models import User
-from rest_framework.authtoken.models import Token
+from django.db import models
-from dashboard.models import Resource
def upload_to(object, filename):
return object.user.username + '/' + filename
self.user1profile.save()
self.client.get(url)
self.assertEqual(timezone.get_current_timezone_name(), 'Etc/Greenwich')
+
+ # if there is no profile for a user, it should be created
+ user2 = User.objects.create(username='user2')
+ user2.set_password('user2')
+ user2.save()
+ self.client.login(username='user2', password='user2')
+ self.client.get(url)
+ self.assertTrue(user2.userprofile)
+
+
from django.urls import reverse
from django.utils.decorators import method_decorator
from django.views.generic import RedirectView, TemplateView, UpdateView
-
from jira import JIRA
from rest_framework.authtoken.models import Token
client.set_signature_method(SignatureMethod_RSA_SHA1())
# Step 1. Get a request token from Jira.
- resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST")
+ try:
+ resp, content = client.request(settings.OAUTH_REQUEST_TOKEN_URL, "POST")
+ except Exception as e:
+ messages.add_message(self.request, messages.ERROR,
+ 'Error: Connection to Jira failed. Please contact an Administrator')
+ return '/'
if resp['status'] != '200':
- raise Exception("Invalid response %s: %s" % (resp['status'], content))
+ messages.add_message(self.request, messages.ERROR,
+ 'Error: Connection to Jira failed. Please contact an Administrator')
+ return '/'
# Step 2. Store the request token in a session for later use.
self.request.session['request_token'] = dict(urllib.parse.parse_qsl(content.decode()))
client.set_signature_method(SignatureMethod_RSA_SHA1())
# Step 2. Request the authorized access token from Jira.
- resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST")
+ try:
+ resp, content = client.request(settings.OAUTH_ACCESS_TOKEN_URL, "POST")
+ except Exception as e:
+ messages.add_message(self.request, messages.ERROR,
+ 'Error: Connection to Jira failed. Please contact an Administrator')
+ return '/'
if resp['status'] != '200':
+ messages.add_message(self.request, messages.ERROR,
+ 'Error: Connection to Jira failed. Please contact an Administrator')
return '/'
access_token = dict(urllib.parse.parse_qsl(content.decode()))
# redirect user to settings page to complete profile
return url
+
@method_decorator(login_required, name='dispatch')
class UserListView(TemplateView):
template_name = "account/user_list.html"
##############################################################################
-from rest_framework import viewsets
-
from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect
from django.utils.decorators import method_decorator
+from django.views import View
+from rest_framework import viewsets
+from rest_framework.authtoken.models import Token
+
from api.serializers import ResourceSerializer, ServerSerializer, BookingSerializer
from booking.models import Booking
from dashboard.models import Resource, Server
-from django.views import View
-from rest_framework.authtoken.models import Token
-from django.shortcuts import redirect
class BookingViewSet(viewsets.ModelViewSet):
##############################################################################
+from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from jira import JIRA
from jira import JIRAError
from dashboard.models import Resource
-from django.conf import settings
+
class Installer(models.Model):
id = models.AutoField(primary_key=True)
from datetime import timedelta
-from django.contrib.auth.models import User, Permission
+from django.contrib.auth.models import Permission
from django.test import TestCase
from django.utils import timezone
-from account.models import UserProfile
-from booking.models import Booking
+from booking.models import *
from dashboard.models import Resource
from jenkins.models import JenkinsSlave
self.user1 = User.objects.get(pk=self.user1.id)
- def test_start__end(self):
+ self.installer = Installer.objects.create(name='TestInstaller')
+ self.scenario = Scenario.objects.create(name='TestScenario')
+
+ def test_start_end(self):
"""
if the start of a booking is greater or equal then the end, saving should raise a
ValueException
user=self.user1, resource=self.res1))
self.assertTrue(
Booking.objects.create(start=start, end=end,
- user=self.user1, resource=self.res2))
-
- def test_authorization(self):
- user = User.objects.create(username='user')
- user.userprofile = UserProfile.objects.create(user=user)
- self.assertRaises(PermissionError, Booking.objects.create, start=timezone.now(),
- end=timezone.now() + timedelta(days=1), resource=self.res1, user=user)
- self.res1.owner = user
- self.assertTrue(
- Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1),
- resource=self.res1, user=user))
- self.res1.owner = self.owner
- user.user_permissions.add(self.add_booking_perm)
- user = User.objects.get(pk=user.id)
- self.assertTrue(
- Booking.objects.create(start=timezone.now(), end=timezone.now() + timedelta(days=1),
- resource=self.res2, user=user))
+ user=self.user1, resource=self.res2, scenario=self.scenario,
+ installer=self.installer))
\ No newline at end of file
from datetime import timedelta
-from django.contrib import auth
from django.test import Client
-from django.utils import timezone
-from django.contrib.auth.models import Permission
from django.test import TestCase
from django.urls import reverse
+from django.utils import timezone
from django.utils.encoding import force_text
from registration.forms import User
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)
self.assertIn('resource', response.context)
+ def test_booking_view(self):
+ start = timezone.now()
+ end = start + timedelta(weeks=1)
+ booking = Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1)
+ url = reverse('booking:detail', kwargs={'booking_id':0})
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 404)
+
+ url = reverse('booking:detail', kwargs={'booking_id':booking.id})
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed('booking/booking_detail.html')
+ self.assertIn('booking', response.context)
+
+ def test_booking_list_view(self):
+ start = timezone.now() - timedelta(weeks=2)
+ end = start + timedelta(weeks=1)
+ Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1)
+
+ url = reverse('booking:list')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed('booking/booking_list.html')
+ self.assertTrue(len(response.context['bookings']) == 0)
+
+ start = timezone.now()
+ end = start + timedelta(weeks=1)
+ Booking.objects.create(start=start, end=end, user=self.user1, resource=self.res1)
+ response = self.client.get(url)
+ self.assertTrue(len(response.context['bookings']) == 1)
\ No newline at end of file
##############################################################################
-from datetime import timedelta
-
from django.conf import settings
from django.contrib import messages
from django.contrib.auth.mixins import LoginRequiredMixin
booking.save()
-class BookingFormView(LoginRequiredMixin, FormView):
+class BookingFormView(FormView):
template_name = "booking/booking_calendar.html"
form_class = BookingForm
return reverse('booking:create', kwargs=self.kwargs)
def form_valid(self, form):
+ if not self.request.user.is_authenticated:
+ messages.add_message(self.request, messages.ERROR,
+ 'You need to be logged in to book a Pod.')
+ return super(BookingFormView, self).form_invalid(form)
+
user = self.request.user
booking = Booking(start=form.cleaned_data['start'],
end=form.cleaned_data['end'],
except ValueError as err:
messages.add_message(self.request, messages.ERROR, err)
return super(BookingFormView, self).form_invalid(form)
- except PermissionError as err:
- messages.add_message(self.request, messages.ERROR, err)
- return super(BookingFormView, self).form_invalid(form)
try:
if settings.CREATE_JIRA_TICKET:
create_jira_ticket(user, booking)
context.update({'title': title, 'booking': booking})
return context
+
class BookingListView(TemplateView):
template_name = "booking/booking_list.html"
from datetime import timedelta
-from django.utils import timezone
from django.contrib.auth.models import User
from django.db import models
+from django.utils import timezone
from jenkins.models import JenkinsSlave
##############################################################################
-from celery import shared_task
from datetime import timedelta
+
+from celery import shared_task
from django.utils import timezone
from jenkins.models import JenkinsStatistic
##############################################################################
-from django.template.defaultfilters import register
-
from django.conf import settings
+from django.template.defaultfilters import register
@register.filter
--- /dev/null
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
--- /dev/null
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from datetime import timedelta
+from math import ceil, floor
+
+from django.test import TestCase
+from django.utils import timezone
+
+from booking.models import *
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+
+
+class ResourceModelTestCase(TestCase):
+ def setUp(self):
+ self.slave = JenkinsSlave.objects.create(name='test', url='test')
+ self.owner = User.objects.create(username='owner')
+
+ self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x',
+ url='x', owner=self.owner)
+
+ def test_booking_utilization(self):
+ utilization = self.res1.get_booking_utilization(1)
+ self.assertTrue(utilization['booked_seconds'] == 0)
+ self.assertTrue(utilization['available_seconds'] == timedelta(weeks=1).total_seconds())
+
+ start = timezone.now() + timedelta(days=1)
+ end = start + timedelta(days=1)
+ booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1,
+ user=self.owner)
+
+ utilization = self.res1.get_booking_utilization(1)
+ booked_seconds = timedelta(days=1).total_seconds()
+ self.assertEqual(utilization['booked_seconds'], booked_seconds)
+
+ utilization = self.res1.get_booking_utilization(-1)
+ self.assertEqual(utilization['booked_seconds'], 0)
+
+ booking.delete()
+ start = timezone.now() - timedelta(days=1)
+ end = start + timedelta(days=2)
+ booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1,
+ user=self.owner)
+ booked_seconds = self.res1.get_booking_utilization(1)['booked_seconds']
+ # use ceil because a fraction of the booked time has already passed now
+ booked_seconds = ceil(booked_seconds)
+ self.assertEqual(booked_seconds, timedelta(days=1).total_seconds())
+
+ booking.delete()
+ start = timezone.now() + timedelta(days=6)
+ end = start + timedelta(days=2)
+ booking = Booking.objects.create(start=start, end=end, purpose='test', resource=self.res1,
+ user=self.owner)
+ booked_seconds = self.res1.get_booking_utilization(1)['booked_seconds']
+ booked_seconds = floor(booked_seconds)
+ self.assertEqual(booked_seconds, timedelta(days=1).total_seconds())
+
+
+
+
+
--- /dev/null
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from django.test import TestCase
+from django.urls import reverse
+
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+
+
+class DashboardViewTestCase(TestCase):
+ def setUp(self):
+ self.slave_active = JenkinsSlave.objects.create(name='slave_active', url='x', active=True)
+ self.slave_inactive = JenkinsSlave.objects.create(name='slave_inactive', url='x',
+ active=False)
+ self.res_active = Resource.objects.create(name='res_active', slave=self.slave_active,
+ description='x', url='x')
+ self.res_inactive = Resource.objects.create(name='res_inactive', slave=self.slave_inactive,
+ description='x', url='x')
+
+ def test_booking_utilization_json(self):
+ url = reverse('dashboard:booking_utilization', kwargs={'resource_id': 0, 'weeks': 0})
+ self.assertEqual(self.client.get(url).status_code, 404)
+
+ url = reverse('dashboard:booking_utilization', kwargs={'resource_id': self.res_active.id,
+ 'weeks': 0})
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'data')
+
+ def test_jenkins_utilization_json(self):
+ url = reverse('dashboard:jenkins_utilization', kwargs={'resource_id': 0, 'weeks': 0})
+ self.assertEqual(self.client.get(url).status_code, 404)
+
+ url = reverse('dashboard:jenkins_utilization', kwargs={'resource_id': self.res_active.id,
+ 'weeks': 0})
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertContains(response, 'data')
+
+ def test_jenkins_slaves_view(self):
+ url = reverse('dashboard:jenkins_slaves')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertIn(self.slave_active, response.context['slaves'])
+ self.assertNotIn(self.slave_inactive, response.context['slaves'])
+
+ def test_ci_pods_view(self):
+ url = reverse('dashboard:ci_pods')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.context['ci_pods']), 0)
+
+ self.slave_active.ci_slave = True
+ self.slave_inactive.ci_slave = True
+ self.slave_active.save()
+ self.slave_inactive.save()
+
+ response = self.client.get(url)
+ self.assertIn(self.res_active, response.context['ci_pods'])
+ self.assertNotIn(self.res_inactive, response.context['ci_pods'])
+
+ def test_dev_pods_view(self):
+ url = reverse('dashboard:dev_pods')
+ response = self.client.get(url)
+ self.assertEqual(response.status_code, 200)
+ self.assertEqual(len(response.context['dev_pods']), 0)
+
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url
+
from dashboard.views import *
urlpatterns = [
template_name = "dashboard/jenkins_slaves.html"
def get_context_data(self, **kwargs):
- slaves = JenkinsSlave.objects.all()
+ slaves = JenkinsSlave.objects.filter(active=True)
context = super(JenkinsSlavesView, self).get_context_data(**kwargs)
context.update({'title': "Jenkins Slaves", 'slaves': slaves})
return context
template_name = "dashboard/ci_pods.html"
def get_context_data(self, **kwargs):
- ci_pods = Resource.objects.filter(slave__ci_slave=True)
+ ci_pods = Resource.objects.filter(slave__ci_slave=True, slave__active=True)
context = super(CIPodsView, self).get_context_data(**kwargs)
context.update({'title': "CI Pods", 'ci_pods': ci_pods})
return context
template_name = "dashboard/dev_pods.html"
def get_context_data(self, **kwargs):
- resources = Resource.objects.filter(slave__dev_pod=True)
+ resources = Resource.objects.filter(slave__dev_pod=True, slave__active=True)
bookings = Booking.objects.filter(start__lte=timezone.now())
bookings = bookings.filter(end__gt=timezone.now())
booking_utilization = resource.get_booking_utilization(weeks=4)
total = booking_utilization['booked_seconds'] + booking_utilization['available_seconds']
try:
- utilization_percentage = "%d%%" % (float(booking_utilization['booked_seconds']) /
+ utilization_percentage = "%d%%" % (float(booking_utilization['booked_seconds']) /
total * 100)
except (ValueError, ZeroDivisionError):
return ""
template_name = "dashboard/resource_all.html"
def get_context_data(self, **kwargs):
- resources = Resource.objects.filter(slave__dev_pod=True)
+ resources = Resource.objects.filter(slave__dev_pod=True, slave__active=True)
pods = []
for resource in resources:
utilization = resource.slave.get_utilization(timedelta(days=7))
import logging
-
import re
+
import requests
from django.core.cache import cache
last_job_installer = models.CharField(max_length=50, default='')
last_job_result = models.CharField(max_length=30, default='')
+ active = models.BooleanField(default=False)
+
def get_utilization(self, timedelta):
"""
Return a dictionary containing the count of idle, online and offline measurements in the time from
from celery import shared_task
+from dashboard.models import Resource
from jenkins.models import JenkinsSlave, JenkinsStatistic
from .adapter import *
def update_jenkins_slaves():
+ JenkinsSlave.objects.all().update(active=False)
+
jenkins_slaves = get_all_slaves()
for slave in jenkins_slaves:
jenkins_slave, created = JenkinsSlave.objects.get_or_create(name=slave['displayName'],
url=get_slave_url(slave))
+ jenkins_slave.active = True
jenkins_slave.ci_slave = is_ci_slave(slave['displayName'])
jenkins_slave.dev_pod = is_dev_pod(slave['displayName'])
jenkins_slave.status = get_slave_status(slave)
+ # if this is a new slave and a pod, check if there is a resource for it, create one if not
+ if created and 'pod' in slave['displayName']:
+ # parse resource name from slave name
+ # naming example: orange-pod1, resource name: Orange POD 1
+ tokens = slave['displayName'].split('-')
+ name = tokens[0].capitalize() + ' POD '# company name
+ name += tokens[1][3:] # remove 'pod'
+ resource, created = Resource.objects.get_or_create(name=name)
+ resource.slave = jenkins_slave
+ resource.save()
+
last_job = get_jenkins_job(jenkins_slave.name)
if last_job is not None:
last_job = parse_job(last_job)
##############################################################################
+from datetime import timedelta
from unittest import TestCase
import jenkins.adapter as jenkins
+from jenkins.models import *
# Tests that the data we get with the jenkinsadapter contains all the
self.assertTrue('idle' in slave)
self.assertTrue('offline' in slave)
+ def test_get_slave(self):
+ slaves = jenkins.get_all_slaves()
+ self.assertEqual(slaves[0], jenkins.get_slave(slaves[0]['displayName']))
+ self.assertEqual({}, jenkins.get_slave('098f6bcd4621d373cade4e832627b4f6'))
+
def test_get_ci_slaves(self):
slaves = jenkins.get_ci_slaves()
self.assertTrue(len(slaves) > 0)
for slave in slaves:
self.assertTrue('nodeName' in slave)
+ def test_get_jenkins_job(self):
+ slaves = jenkins.get_ci_slaves()
+ job = None
+ for slave in slaves:
+ job = jenkins.get_jenkins_job(slave['nodeName'])
+ if job is not None:
+ break
+ # We need to test at least one job
+ self.assertNotEqual(job, None)
+
def test_get_all_jobs(self):
jobs = jenkins.get_all_jobs()
lastBuild = False
self.assertTrue('timestamp' in job['lastBuild'])
self.assertTrue('builtOn' in job['lastBuild'])
self.assertTrue(lastBuild)
+
+ def test_parse_job(self):
+ job = {
+ "displayName": "apex-deploy-baremetal-os-nosdn-fdio-noha-colorado",
+ "url": "https://build.opnfv.org/ci/job/apex-deploy-baremetal-os-nosdn-fdio-noha-colorado/",
+ "lastBuild": {
+ "building": False,
+ "fullDisplayName": "apex-deploy-baremetal-os-nosdn-fdio-noha-colorado #37",
+ "result": "SUCCESS",
+ "timestamp": 1476283629917,
+ "builtOn": "lf-pod1"
+ }
+ }
+
+ job = jenkins.parse_job(job)
+ self.assertEqual(job['scenario'], 'os-nosdn-fdio-noha')
+ self.assertEqual(job['installer'], 'apex')
+ self.assertEqual(job['branch'], 'colorado')
+ self.assertEqual(job['result'], 'SUCCESS')
+ self.assertEqual(job['building'], False)
+ self.assertEqual(job['url'],
+ "https://build.opnfv.org/ci/job/apex-deploy-baremetal-os-nosdn-fdio-noha-colorado/")
+ self.assertEqual(job['name'],
+ 'apex-deploy-baremetal-os-nosdn-fdio-noha-colorado')
+
+ def test_get_slave_status(self):
+ slave = {
+ 'offline': True,
+ 'idle': False
+ }
+ self.assertEqual(jenkins.get_slave_status(slave), 'offline')
+ slave = {
+ 'offline': False,
+ 'idle': False
+ }
+ self.assertEqual(jenkins.get_slave_status(slave), 'online')
+ slave = {
+ 'offline': False,
+ 'idle': True
+ }
+ self.assertEqual(jenkins.get_slave_status(slave), 'online / idle')
+
+
+class JenkinsModelTestCase(TestCase):
+ def test_get_utilization(self):
+ jenkins_slave = JenkinsSlave.objects.create(name='test', status='offline', url='')
+ utilization = jenkins_slave.get_utilization(timedelta(weeks=1))
+ self.assertEqual(utilization['idle'], 0)
+ self.assertEqual(utilization['offline'], 0)
+ self.assertEqual(utilization['online'], 0)
+
+ for i in range(10):
+ JenkinsStatistic.objects.create(slave=jenkins_slave,
+ offline=True, idle=True,
+ online=True)
+
+ utilization = jenkins_slave.get_utilization(timedelta(weeks=1))
+ self.assertEqual(utilization['idle'], 10)
+ self.assertEqual(utilization['offline'], 10)
+ self.assertEqual(utilization['online'], 10)
}
def save(self, *args, **kwargs):
- notifications = self.booking.bookingnotification_set.filter(type=self.type)
- if notifications.count() > 1:
+ notifications = self.booking.bookingnotification_set.filter(type=self.type).exclude(
+ id=self.id)
+ if notifications.count() > 0:
raise ValueError('Doubled Notification')
return super(BookingNotification, self).save(*args, **kwargs)
\ No newline at end of file
##############################################################################
-from celery import shared_task
from datetime import timedelta
+from celery import shared_task
from django.conf import settings
from django.utils import timezone
--- /dev/null
+##############################################################################
+# Copyright (c) 2016 Max Breitenfeldt and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from datetime import timedelta
+from unittest import TestCase
+
+from django.contrib.auth.models import User
+from django.utils import timezone
+
+from booking.models import Booking
+from dashboard.models import Resource
+from jenkins.models import JenkinsSlave
+from notification.models import *
+
+
+class JenkinsModelTestCase(TestCase):
+ def setUp(self):
+ self.slave = JenkinsSlave.objects.create(name='test1', url='test')
+ self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x',
+ url='x')
+ self.user1 = User.objects.create(username='user1')
+
+ start = timezone.now()
+ end = start + timedelta(days=1)
+ self.booking = Booking.objects.create(start=start, end=end, purpose='test',
+ resource=self.res1, user=self.user1)
+
+ def test_booking_notification(self):
+ BookingNotification.objects.create(type='test', booking=self.booking,
+ submit_time=timezone.now())
+
+ self.assertRaises(ValueError, BookingNotification.objects.create, type='test',
+ booking=self.booking,
+ submit_time=timezone.now())
<i class="fa fa-edit fa-fw"></i>Booking
</div>
<div class="panel-body">
- <div id="booking_form_div">
- {% bootstrap_form_errors form type='non_fields' %}
- <form method="post" action="" class="form" id="bookingform">
- {% csrf_token %}
+ {% if user.is_authenticated %}
+ <div id="booking_form_div">
+ {% bootstrap_form_errors form type='non_fields' %}
+ <form method="post" action="" class="form" id="bookingform">
+ {% csrf_token %}
- <div class='input-group' id='starttimepicker'>
- {% bootstrap_field form.start addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
- </div>
- <div class='input-group' id='endtimepicker'>
- {% bootstrap_field form.end addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
- </div>
- {% bootstrap_field form.purpose %}
- {% bootstrap_field form.installer %}
- {% bootstrap_field form.scenario %}
- {% buttons %}
- <button type="submit" class="btn btn btn-success">
- Book
- </button>
- {% endbuttons %}
- </form>
- </div>
+ <div class='input-group' id='starttimepicker'>
+ {% bootstrap_field form.start addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
+ </div>
+ <div class='input-group' id='endtimepicker'>
+ {% bootstrap_field form.end addon_after='<span class="glyphicon glyphicon-calendar"></span>' %}
+ </div>
+ {% bootstrap_field form.purpose %}
+ {% bootstrap_field form.installer %}
+ {% bootstrap_field form.scenario %}
+ {% buttons %}
+ <button type="submit" class="btn btn btn-success">
+ Book
+ </button>
+ {% endbuttons %}
+ </form>
+ </div>
+ {% else %}
+ <p>Please
+ <a href="{% url 'account:login' %}">
+ login with Jira</a>
+ to book this Pod</p>
+ {% endif %}
</div>
</div>
</div>