Create Jira issue for new booking 37/19237/1
authormaxbr <maxbr@mi.fu-berlin.de>
Mon, 22 Aug 2016 16:59:47 +0000 (18:59 +0200)
committermaxbr <maxbr@mi.fu-berlin.de>
Mon, 22 Aug 2016 17:04:19 +0000 (19:04 +0200)
JIRA: RELENG-12

The issue is assigned to the lab owner and to the POD Access Request
Component. The pgp and ssh keys are uploaded to jira as an attachement.

Signed-off-by: maxbr <maxbr@mi.fu-berlin.de>
15 files changed:
tools/pharos-dashboard/account/forms.py
tools/pharos-dashboard/account/models.py
tools/pharos-dashboard/booking/models.py
tools/pharos-dashboard/booking/tests/test_models.py
tools/pharos-dashboard/booking/tests/test_views.py
tools/pharos-dashboard/booking/views.py
tools/pharos-dashboard/dashboard/fixtures/dashboard.json
tools/pharos-dashboard/dashboard/models.py
tools/pharos-dashboard/dashboard/views.py
tools/pharos-dashboard/jenkins/models.py
tools/pharos-dashboard/pharos_dashboard/settings.py
tools/pharos-dashboard/templates/account/userprofile_update_form.html
tools/pharos-dashboard/templates/booking/booking_form.html [deleted file]
tools/pharos-dashboard/templates/registration/login.html [deleted file]
tools/pharos-dashboard/templates/registration/registration_form.html [deleted file]

index 14f11cd..92c55d8 100644 (file)
@@ -9,6 +9,4 @@ class AccountSettingsForm(forms.ModelForm):
         model = UserProfile
         fields = ['company', 'ssh_public_key', 'pgp_public_key', 'timezone']
 
-    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')
index fbabf6c..fb2c8dd 100644 (file)
@@ -4,12 +4,14 @@ from django.contrib.auth.models import User
 
 from dashboard.models import Resource
 
+def upload_to(object, filename):
+    return object.user.username + '/' + filename
 
 class UserProfile(models.Model):
     user = models.OneToOneField(User, on_delete=models.CASCADE)
     timezone = models.CharField(max_length=100, blank=False, default='UTC')
-    ssh_public_key = models.CharField(max_length=2048, blank=False)
-    pgp_public_key = models.CharField(max_length=2048, blank=False)
+    ssh_public_key = models.FileField(upload_to=upload_to, null=True, blank=True)
+    pgp_public_key = models.FileField(upload_to=upload_to, null=True, blank=True)
     company = models.CharField(max_length=200, blank=False)
     oauth_token = models.CharField(max_length=1024, blank=False)
     oauth_secret = models.CharField(max_length=1024, blank=False)
index 719dd9b..8011fa4 100644 (file)
@@ -10,6 +10,7 @@ class Booking(models.Model):
     resource = models.ForeignKey(Resource, models.PROTECT)
     start = models.DateTimeField()
     end = models.DateTimeField()
+    jira_issue_id = models.IntegerField(null=True)
 
     purpose = models.CharField(max_length=300, blank=False)
 
@@ -25,11 +26,10 @@ class Booking(models.Model):
         if user.has_perm('booking.add_booking'):
             return True
         # Check if User owns this resource
-        if user in self.resource.owners.all():
+        if user == self.resource.owner:
             return True
         return False
 
-
     def save(self, *args, **kwargs):
         """
         Save the booking if self.user is authorized and there is no overlapping booking.
index 00f6b26..7a572c5 100644 (file)
@@ -4,6 +4,7 @@ from django.contrib.auth.models import User, Permission
 from django.test import TestCase
 from django.utils import timezone
 
+from account.models import UserProfile
 from booking.models import Booking
 from dashboard.models import Resource
 from jenkins.models import JenkinsSlave
@@ -12,9 +13,12 @@ from jenkins.models import JenkinsSlave
 class BookingModelTestCase(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')
-        self.res2 = Resource.objects.create(name='res2', slave=self.slave, description='x', url='x')
+        self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x',
+                                            url='x',owner=self.owner)
+        self.res2 = Resource.objects.create(name='res2', slave=self.slave, description='x',
+                                            url='x',owner=self.owner)
 
         self.user1 = User.objects.create(username='user1')
 
@@ -78,12 +82,14 @@ class BookingModelTestCase(TestCase):
 
     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.owners.add(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(
index b0c4b49..c5dff58 100644 (file)
@@ -19,7 +19,9 @@ class BookingViewTestCase(TestCase):
     def setUp(self):
         self.client = Client()
         self.slave = JenkinsSlave.objects.create(name='test', url='test')
-        self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x', url='x')
+        self.owner = User.objects.create(username='owner')
+        self.res1 = Resource.objects.create(name='res1', slave=self.slave, description='x',
+                                            url='x',owner=self.owner)
         self.user1 = User.objects.create(username='user1')
         self.user1.set_password('user1')
         self.user1profile = UserProfile.objects.create(user=self.user1)
index c461aef..c2f437f 100644 (file)
@@ -5,6 +5,8 @@ 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 django.shortcuts import redirect
+from jira import JIRAError
 
 from account.jira_util import get_jira
 from booking.forms import BookingForm
@@ -12,20 +14,26 @@ from booking.models import Booking
 from dashboard.models import Resource
 
 
+def create_jira_ticket(user, booking):
+    jira = get_jira(user)
+    issue_dict = {
+        'project': 'PHAROS',
+        'summary': str(booking.resource) + ': Access Request',
+        'description': booking.purpose,
+        'issuetype': {'name': 'Task'},
+        'components': [{'name': 'POD Access Request'}],
+        'assignee': {'name': booking.resource.owner.username}
+    }
+    issue = jira.create_issue(fields=issue_dict)
+    jira.add_attachment(issue, user.userprofile.pgp_public_key)
+    jira.add_attachment(issue, user.userprofile.ssh_public_key)
+    booking.jira_issue_id = issue.id
+
+
 class BookingFormView(LoginRequiredMixin, FormView):
     template_name = "booking/booking_calendar.html"
     form_class = BookingForm
 
-    def open_jira_issue(self,booking):
-        jira = get_jira(self.request.user)
-        issue_dict = {
-            'project': 'PHAROS',
-            'summary': 'Booking: ' + str(self.resource),
-            'description': str(booking),
-            'issuetype': {'name': 'Task'},
-        }
-        jira.create_issue(fields=issue_dict)
-
     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)
@@ -40,9 +48,14 @@ class BookingFormView(LoginRequiredMixin, FormView):
         return reverse('booking:create', kwargs=self.kwargs)
 
     def form_valid(self, form):
+        user = self.request.user
+        if not user.userprofile.ssh_public_key or not user.userprofile.pgp_public_key:
+            messages.add_message(self.request, messages.INFO,
+                                 'Please upload your private keys before booking')
+            return redirect('account:settings')
         booking = Booking(start=form.cleaned_data['start'], end=form.cleaned_data['end'],
                           purpose=form.cleaned_data['purpose'], resource=self.resource,
-                          user=self.request.user)
+                          user=user)
         try:
             booking.save()
         except ValueError as err:
@@ -51,7 +64,14 @@ class BookingFormView(LoginRequiredMixin, FormView):
         except PermissionError as err:
             messages.add_message(self.request, messages.ERROR, err)
             return super(BookingFormView, self).form_invalid(form)
-        self.open_jira_issue(booking)
+        try:
+            create_jira_ticket(user, booking)
+        except JIRAError:
+            messages.add_message(self.request, messages.ERROR, 'Failed to create Jira Ticket. '
+                                                               'Please check your Jira '
+                                                               'permissions.')
+            booking.delete()
+            return super(BookingFormView, self).form_invalid(form)
         messages.add_message(self.request, messages.SUCCESS, 'Booking saved')
         return super(BookingFormView, self).form_valid(form)
 
index d90e99b..f0ac3b2 100644 (file)
@@ -4,7 +4,6 @@
     "pk": 1,
     "fields": {
         "name": "Linux Foundation POD 1",
-        "slavename": "lf-pod1",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab"
     }
@@ -14,7 +13,6 @@
     "pk": 2,
     "fields": {
         "name": "Linux Foundation POD 2",
-        "slavename": "lf-pod2",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Lf+Lab"
     }
@@ -24,7 +22,6 @@
     "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"
     }
@@ -34,7 +31,6 @@
     "pk": 4,
     "fields": {
         "name": "Intel POD 2",
-        "slavename": "intel-pod2",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod2"
     }
@@ -44,7 +40,6 @@
     "pk": 5,
     "fields": {
         "name": "Intel POD 5",
-        "slavename": "intel-pod5",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod5"
     }
@@ -54,7 +49,6 @@
     "pk": 6,
     "fields": {
         "name": "Intel POD 6",
-        "slavename": "intel-pod6",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod6"
     }
@@ -64,7 +58,6 @@
     "pk": 7,
     "fields": {
         "name": "Intel POD 8",
-        "slavename": "intel-pod8",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod8"
     }
@@ -74,7 +67,6 @@
     "pk": 8,
     "fields": {
         "name": "Huawei POD 1",
-        "slavename": "huawei-pod1",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
     }
@@ -84,7 +76,6 @@
     "pk": 9,
     "fields": {
         "name": "Intel POD 3",
-        "slavename": "intel-pod3",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod3"
     }
@@ -94,7 +85,6 @@
     "pk": 10,
     "fields": {
         "name": "Dell POD 1",
-        "slavename": "dell-pod1",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting"
     }
     "pk": 11,
     "fields": {
         "name": "Dell POD 2",
-        "slavename": "dell-pod2",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Dell+Hosting"
     }
     "pk": 12,
     "fields": {
         "name": "Orange POD 2",
-        "slavename": "orange-pod2",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Opnfv-orange-pod2"
     }
     "pk": 13,
     "fields": {
         "name": "Arm POD 1",
-        "slavename": "arm-build1",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Enea-pharos-lab"
     }
     "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"
     }
     "pk": 15,
     "fields": {
         "name": "Huawei POD 2",
-        "slavename": "huawei-pod2",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
     }
     "pk": 16,
     "fields": {
         "name": "Huawei POD 3",
-        "slavename": "huawei-pod3",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
     }
     "pk": 17,
     "fields": {
         "name": "Huawei POD 4",
-        "slavename": "huawei-pod4",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Huawei+Hosting"
     }
     "pk": 18,
     "fields": {
         "name": "Intel POD 9",
-        "slavename": "intel-pod9",
         "description": "Some description",
         "url": "https://wiki.opnfv.org/display/pharos/Intel+Pod9"
     }
index 02073e6..971af6a 100644 (file)
@@ -10,8 +10,8 @@ class Resource(models.Model):
     name = models.CharField(max_length=100, unique=True)
     description = models.CharField(max_length=300, blank=True, null=True)
     url = models.CharField(max_length=100, blank=True, null=True)
-    owners = models.ManyToManyField(User)
-    slave = models.ForeignKey(JenkinsSlave, on_delete=models.DO_NOTHING)
+    owner = models.ForeignKey(User)
+    slave = models.ForeignKey(JenkinsSlave, on_delete=models.DO_NOTHING, null=True)
 
     class Meta:
         db_table = 'resource'
index 6af2c1a..56b3a51 100644 (file)
@@ -1,4 +1,6 @@
 from datetime import timedelta
+
+from django.contrib.auth.models import User
 from django.utils import timezone
 from django.views.generic import TemplateView
 
index f7d54c9..438a882 100644 (file)
@@ -21,6 +21,9 @@ class JenkinsSlave(models.Model):
     class Meta:
         db_table = 'jenkins_slave'
 
+    def __str__(self):
+        return self.name
+
 
 class JenkinsStatistic(models.Model):
     id = models.AutoField(primary_key=True)
index a482f95..3678b03 100644 (file)
@@ -123,6 +123,8 @@ USE_L10N = True
 
 USE_TZ = True
 
+MEDIA_ROOT = '/home/max/tmp/django_media/'
+
 # Static files (CSS, JavaScript, Images)
 # https://docs.djangoproject.com/en/1.10/howto/static-files/
 
index 0a921d5..542ea81 100644 (file)
                         </h3>
                     </div>
                     <div class="panel-body">
-                        <form method="post" action="">
+                        <form enctype="multipart/form-data" method="post">
                             {% csrf_token %}
                             {% bootstrap_form form %}
                             {% buttons %}
                                 <button type="submit" class="btn btn btn-success">
-                                    Submit
+                                    Save
                                 </button>
                             {% endbuttons %}
                         </form>
diff --git a/tools/pharos-dashboard/templates/booking/booking_form.html b/tools/pharos-dashboard/templates/booking/booking_form.html
deleted file mode 100644 (file)
index f13c4b4..0000000
+++ /dev/null
@@ -1,21 +0,0 @@
-{% 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/registration/login.html b/tools/pharos-dashboard/templates/registration/login.html
deleted file mode 100644 (file)
index 45b9d45..0000000
+++ /dev/null
@@ -1,51 +0,0 @@
-{% extends "layout.html" %}
-{% load bootstrap3 %}
-
-{% block basecontent %}
-    <div class="container">
-        <div class="row">
-            <div class="col-md-4 col-md-offset-4">
-                {% if next %}
-                    <div class="alert alert-dismissable alert-info">
-                        <button type="button" class="close" data-dismiss="alert" aria-label="Close">
-                            <span aria-hidden="true">&times;</span>
-                        </button>
-                        {% if user.is_authenticated %}
-                            Your account doesn't have access to this page. To proceed,
-                            please login with an account that has access.
-                        {% else %}
-                            Please login to see this page.
-                        {% endif %}
-                    </div>
-                {% endif %}
-            </div>
-        </div>
-        <div class="row">
-            <div class="col-md-4 col-md-offset-4">
-                <div class="login-panel panel panel-default">
-                    <div class="panel-heading">
-                        <h3 class="panel-title">
-                            Login
-                        </h3>
-                    </div>
-                    <div class="panel-body">
-                        <form method="post">
-                            {% csrf_token %}
-                            {% 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
deleted file mode 100644 (file)
index 0a921d5..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-{% 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 %}