Commit 9e18bdbf by Szeberényi Imre

Merge branch 'editable_request' into 'master'

Editable request

See merge request !410
parents c62edb89 243a0c02
Pipeline #1346 passed with stage
in 0 seconds
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>. # with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.forms import ( from django.forms import (
ModelForm, ModelChoiceField, ChoiceField, Form, CharField, RadioSelect, ModelForm, ModelChoiceField, ChoiceField, Form, CharField, RadioSelect,
Textarea, ValidationError Textarea, ValidationError, TextInput, IntegerField, EmailField
) )
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string from django.template.loader import render_to_string
...@@ -27,11 +27,78 @@ from crispy_forms.helper import FormHelper ...@@ -27,11 +27,78 @@ from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit from crispy_forms.layout import Submit
from request.models import ( from request.models import (
LeaseType, TemplateAccessType, TemplateAccessAction, LeaseType, TemplateAccessType, TemplateAccessAction, RequestField
) )
from dashboard.forms import VmResourcesForm from dashboard.forms import VmResourcesForm
class RequestFieldModelForm(ModelForm):
class Meta:
model = RequestField
fields = '__all__'
widgets = {
'choices': TextInput(attrs={'placeholder': 'Optional'}),
}
def __init__(self, *args, **kwargs):
super(RequestFieldModelForm, self).__init__(*args, **kwargs)
self.fields['choices'].required = False
@property
def helper(self):
helper = FormHelper()
return helper
def clean(self):
cleaned_data = super(RequestFieldModelForm, self).clean()
if cleaned_data['type'] == 'Email' and cleaned_data['choices']:
raise ValidationError(_("Email field can't have choices!"))
if cleaned_data['type'] == 'Integer':
for choice in cleaned_data['choices'].split(','):
try:
int(choice)
except:
raise ValidationError(_("IntegerField choices must be \
integers"))
class EditableForm(Form):
def __init__(self, *args, **kwargs):
type = kwargs.pop('type', None)
kwargs.pop("request", None)
super(EditableForm, self).__init__(*args, **kwargs)
fields = RequestField.objects.filter(request_type=type)
n = 0
if fields:
for field in fields:
n = n+1
type = field.type
if(type == 'Char'):
self.fields['field'+str(n)] = CharField(
max_length=30,
required=field.required,
label=field.fieldname)
elif (type == 'Integer'):
self.fields['field'+str(n)] = IntegerField(
required=field.required,
label=field.fieldname)
elif (type == 'Email'):
self.fields['field'+str(n)] = EmailField(
required=field.required,
label=field.fieldname)
if(field.choices):
choices = [(ch, ch)for ch in field.choices.split(',')]
self.fields['field'+str(n)] = ChoiceField(
choices=choices,
required=field.required,
label=field.fieldname)
def get_dynamic_fields(self):
for field_name in self.fields:
if field_name.startswith("field"):
yield self[field_name]
class LeaseTypeForm(ModelForm): class LeaseTypeForm(ModelForm):
@property @property
def helper(self): def helper(self):
...@@ -80,27 +147,18 @@ class InitialFromFileMixin(object): ...@@ -80,27 +147,18 @@ class InitialFromFileMixin(object):
return message.strip() return message.strip()
class TemplateRequestForm(InitialFromFileMixin, Form): class TemplateRequestForm(EditableForm):
message = CharField(widget=Textarea, label=_("Message"))
template = ModelChoiceField(TemplateAccessType.objects.all(), template = ModelChoiceField(TemplateAccessType.objects.all(),
label=_("Template share")) label=_("Template share"))
level = ChoiceField(TemplateAccessAction.LEVELS, widget=RadioSelect, level = ChoiceField(TemplateAccessAction.LEVELS, widget=RadioSelect,
initial=TemplateAccessAction.LEVELS.user) initial=TemplateAccessAction.LEVELS.user)
initial_template = "request/initials/template.html"
class LeaseRequestForm(InitialFromFileMixin, Form): class LeaseRequestForm(EditableForm):
lease = ModelChoiceField(LeaseType.objects.all(), label=_("Lease")) lease = ModelChoiceField(LeaseType.objects.all(), label=_("Lease"))
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/lease.html"
class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm): class ResourceRequestForm(EditableForm, VmResourcesForm):
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/resources.html"
def clean(self): def clean(self):
cleaned_data = super(ResourceRequestForm, self).clean() cleaned_data = super(ResourceRequestForm, self).clean()
......
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-11-12 15:20
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('request', '0004_auto_20150629_1605'),
]
operations = [
migrations.CreateModel(
name='RequestField',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('fieldname', models.CharField(max_length=50, unique=True)),
('type', models.CharField(choices=[(b'Char', b'CharField'), (b'Integer', b'IntegerField'), (b'Email', b'EmailField')], default=b'Char', max_length=20)),
('choices', models.CharField(max_length=100, null=True)),
('required', models.BooleanField(default=True)),
],
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-11-15 15:48
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('request', '0005_requestfield'),
]
operations = [
migrations.AlterField(
model_name='requestfield',
name='choices',
field=models.CharField(max_length=300, null=True),
),
]
# -*- coding: utf-8 -*-
# Generated by Django 1.11.6 on 2018-12-12 14:12
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('request', '0006_auto_20181115_1548'),
]
operations = [
migrations.AddField(
model_name='requestfield',
name='request_type',
field=models.CharField(choices=[(b'resource', 'resource request'), (b'lease', 'lease request'), (b'template', 'template access request'), (b'resize', 'disk resize request')], default=b'template', max_length=20),
),
]
...@@ -19,6 +19,7 @@ import logging ...@@ -19,6 +19,7 @@ import logging
from django.db.models import ( from django.db.models import (
Model, CharField, IntegerField, TextField, ForeignKey, ManyToManyField, Model, CharField, IntegerField, TextField, ForeignKey, ManyToManyField,
BooleanField,
) )
from django.db.models.signals import post_save from django.db.models.signals import post_save
from django.conf import settings from django.conf import settings
...@@ -157,6 +158,27 @@ class Request(TimeStampedModel): ...@@ -157,6 +158,27 @@ class Request(TimeStampedModel):
return self.action.is_acceptable() return self.action.is_acceptable()
class RequestField(Model):
TYPES = (
('Char', 'CharField'),
('Integer', 'IntegerField'),
('Email', 'EmailField')
)
fieldname = CharField(max_length=50, blank=False, unique=True)
type = CharField(choices=TYPES, default='Char', max_length=20)
request_type = CharField(choices=Request.TYPES, default='template',
max_length=20)
choices = CharField(max_length=300, null=True)
required = BooleanField(default=True)
def __unicode__(self):
return self.fieldname
def get_absolute_url(self):
return reverse('fields_detail', kwargs={'pk': self.pk})
class LeaseType(RequestType): class LeaseType(RequestType):
lease = ForeignKey(Lease, verbose_name=_("Lease")) lease = ForeignKey(Lease, verbose_name=_("Lease"))
......
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading"></div>
<div class="panel-body">
<form action={% url 'request.views.request-field-add' %} method="post">
{% include "display-form-errors.html" %}
{% csrf_token %}
{% for field in form %}
{{ field|as_crispy_field}}
{% endfor %}
<button type="submit" class="btn btn-sm btn-success">{% trans "Add" %}</button></td>
</form>
</div> <!-- .panel-body -->
</div>
</div>
</div>
{% endblock %}
...@@ -6,5 +6,8 @@ ...@@ -6,5 +6,8 @@
{% csrf_token %} {% csrf_token %}
{{ form.lease|as_crispy_field }} {{ form.lease|as_crispy_field }}
{{ form.message|as_crispy_field }} {{ form.message|as_crispy_field }}
{% for fields in form.get_dynamic_fields %}
{{ field|as_crispy_field }}
{% endfor %}
<input type="submit" class="btn btn-primary"/> <input type="submit" class="btn btn-primary"/>
</form> </form>
...@@ -21,6 +21,8 @@ ...@@ -21,6 +21,8 @@
</label> </label>
</div> </div>
{% endfor %} {% endfor %}
{{ form.message|as_crispy_field }} {% for field in form.get_dynamic_fields %}
{{ field|as_crispy_field }}
{% endfor %}
<input type="submit" class="btn btn-primary"/> <input type="submit" class="btn btn-primary"/>
</form> </form>
{% extends "dashboard/base.html" %}
{% load i18n %}
{% load crispy_forms_field %}
{% block content %}
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="no-margin"><i class="fa fa-phone"></i> {% trans "Request fields" %}</h3>
</div>
<div class="panel-body">
<div class="table-responsive">
<div class="table-container">
<table class="text-center table table-bordered table-striped table-hover" >
<thead class"align-center">
<tr>
<th class="text-center">{% trans "Fieldname" %}</th>
<th class="text-center">{% trans "Type" %}</th>
<th class="text-center">{% trans "RequestType" %}</th>
<th class="text-center">{% trans "Choices" %}</th>
<th class="text-center">{% trans "Required" %}</th>
<th class="text-center">{% trans "Delete" %}</th>
</tr>
</thead>
<tbody>
{% for field in object_list %}
<tr>
<td>{{field.fieldname}}</td>
<td>{{field.type}}</td>
<td>{{field.request_type}}</td>
<td>{{field.choices}}</td>
<td>{{field.required}}</td>
<td><a href={% url "request.views.field-delete" pk=field.pk %}>
<i class="fa fa-times"></i></a>
</td>
</tr>
{% endfor %}
<tr>
<form action={% url 'request.views.request-field-add' %} method="post">
{% csrf_token %}
{% for field in add_form %}
<td> {% crispy_field field %}</td>
{% endfor %}
<td><button type="submit" class="btn btn-sm btn-success">{% trans "Add" %}</button></td>
</form>
</tr>
</tbody>
</table>
</div>
</div> <!-- .table-responsive -->
</div> <!-- .panel-body -->
</div>
</div>
</div>
{% endblock %}
...@@ -11,9 +11,14 @@ ...@@ -11,9 +11,14 @@
<div class="col-md-12"> <div class="col-md-12">
<div class="panel panel-default"> <div class="panel panel-default">
<div class="panel-heading"> <div class="panel-heading">
<a class="btn btn-xs btn-primary pull-right "href="{% url "request.views.type-list" %}"> <div class="pull-right">
{% trans "Request types" %} <a class="btn btn-xs btn-primary"href="{% url "request.views.type-list" %}">
</a> {% trans "Request types" %}
</a>
<a class="btn btn-xs btn-success" href="{% url "request.views.field-list" %}">
{% trans "Request fields" %}
</a>
</div>
<h3 class="no-margin"><i class="fa fa-phone"></i> {% trans "Requests" %}</h3> <h3 class="no-margin"><i class="fa fa-phone"></i> {% trans "Requests" %}</h3>
</div> </div>
<div class="panel-body"> <div class="panel-body">
......
...@@ -24,7 +24,9 @@ ...@@ -24,7 +24,9 @@
</div> </div>
{% include "display-form-errors.html" %} {% include "display-form-errors.html" %}
{% include "dashboard/_resources-sliders.html" with field_priority=form.priority field_num_cores=form.num_cores field_ram_size=form.ram_size %} {% include "dashboard/_resources-sliders.html" with field_priority=form.priority field_num_cores=form.num_cores field_ram_size=form.ram_size %}
{{ form.message|as_crispy_field }} {% for field in form.get_dynamic_fields %}
{{ field|as_crispy_field }}
{% endfor %}
<button type="submit" class="btn btn-success"> <button type="submit" class="btn btn-success">
{% trans "Request new resources" %} {% trans "Request new resources" %}
</button> </button>
......
...@@ -25,7 +25,7 @@ from mock import Mock, patch ...@@ -25,7 +25,7 @@ from mock import Mock, patch
from common.tests.celery_mock import MockCeleryMixin from common.tests.celery_mock import MockCeleryMixin
from vm.models import Instance, InstanceTemplate, Lease from vm.models import Instance, InstanceTemplate, Lease
from dashboard.models import Profile from dashboard.models import Profile
from request.models import Request, LeaseType, TemplateAccessType from request.models import Request, LeaseType, TemplateAccessType, RequestField
from dashboard.tests.test_views import LoginMixin from dashboard.tests.test_views import LoginMixin
from vm.operations import ResourcesOperation from vm.operations import ResourcesOperation
...@@ -67,12 +67,16 @@ class RequestTest(LoginMixin, MockCeleryMixin, TestCase): ...@@ -67,12 +67,16 @@ class RequestTest(LoginMixin, MockCeleryMixin, TestCase):
inst = Instance.objects.get(pk=1) inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner') inst.set_level(self.u1, 'owner')
field = RequestField(fieldname="Oka", type="Char",
request_type="resource", required=True)
field.save()
req_count = Request.objects.count() req_count = Request.objects.count()
resp = c.post("/request/resource/1/", { resp = c.post("/request/resource/1/", {
'num_cores': 5, 'num_cores': 5,
'ram_size': 512, 'ram_size': 512,
'priority': 30, 'priority': 30,
'message': "szia", 'field1': "szia",
}) })
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
self.assertEqual(req_count + 1, Request.objects.count()) self.assertEqual(req_count + 1, Request.objects.count())
...@@ -104,17 +108,25 @@ class RequestTest(LoginMixin, MockCeleryMixin, TestCase): ...@@ -104,17 +108,25 @@ class RequestTest(LoginMixin, MockCeleryMixin, TestCase):
template = InstanceTemplate.objects.get(pk=1) template = InstanceTemplate.objects.get(pk=1)
self.assertFalse(template.has_level(self.u1, "user")) self.assertFalse(template.has_level(self.u1, "user"))
field = RequestField(fieldname="Tanszek", type="Char",
request_type="template", required=True)
field.save()
field = RequestField(fieldname="Szobaszam", type="Integer",
request_type="template", required=False)
field.save()
req_count = Request.objects.count() req_count = Request.objects.count()
resp = c.post("/request/template/", { resp = c.post("/request/template/", {
'template': 1, 'template': 1,
'level': "user", 'level': "user",
'message': "szia", 'field1': "IIT",
'field2': 10,
}) })
self.assertEqual(resp.status_code, 302) self.assertEqual(resp.status_code, 302)
self.assertEqual(req_count + 1, Request.objects.count()) self.assertEqual(req_count + 1, Request.objects.count())
new_request = Request.objects.latest("pk") new_request = Request.objects.latest("pk")
self.assertEqual(new_request.status, "PENDING") self.assertEqual(new_request.status, "PENDING")
self.assertEqual(new_request.message, "Tanszek: IIT\nSzobaszam: 10\n")
new_request.accept(self.us) new_request.accept(self.us)
new_request = Request.objects.latest("pk") new_request = Request.objects.latest("pk")
......
...@@ -24,6 +24,8 @@ from .views import ( ...@@ -24,6 +24,8 @@ from .views import (
TemplateAccessTypeCreate, TemplateAccessTypeDetail, TemplateAccessTypeCreate, TemplateAccessTypeDetail,
TemplateRequestView, LeaseRequestView, ResourceRequestView, TemplateRequestView, LeaseRequestView, ResourceRequestView,
LeaseTypeDelete, TemplateAccessTypeDelete, ResizeRequestView, LeaseTypeDelete, TemplateAccessTypeDelete, ResizeRequestView,
RequestFieldFormView, RequestFieldListView, RequestFieldDetailView,
RequestFieldDeleteView,
) )
urlpatterns = [ urlpatterns = [
...@@ -35,6 +37,15 @@ urlpatterns = [ ...@@ -35,6 +37,15 @@ urlpatterns = [
url(r'^type/list/$', RequestTypeList.as_view(), url(r'^type/list/$', RequestTypeList.as_view(),
name="request.views.type-list"), name="request.views.type-list"),
url(r'fields/add/$', RequestFieldFormView.as_view(),
name='request.views.request-field-add'),
url(r'fields/$', RequestFieldListView.as_view(),
name='request.views.field-list'),
url(r'fields/field/(?P<pk>[0-9]+)/$', RequestFieldDetailView.as_view(),
name='request.views.fields-detail'),
url(r'^field/delete/(?P<pk>\d+)/$', RequestFieldDeleteView.as_view(),
name='request.views.field-delete'),
# request types # request types
url(r'^type/lease/create/$', LeaseTypeCreate.as_view(), url(r'^type/lease/create/$', LeaseTypeCreate.as_view(),
name="request.views.lease-type-create"), name="request.views.lease-type-create"),
......
...@@ -18,6 +18,7 @@ from __future__ import unicode_literals, absolute_import ...@@ -18,6 +18,7 @@ from __future__ import unicode_literals, absolute_import
from django.views.generic import ( from django.views.generic import (
UpdateView, TemplateView, DetailView, CreateView, FormView, DeleteView, UpdateView, TemplateView, DetailView, CreateView, FormView, DeleteView,
ListView
) )
from django.contrib import messages from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
...@@ -32,7 +33,7 @@ from django_tables2 import SingleTableView ...@@ -32,7 +33,7 @@ from django_tables2 import SingleTableView
from request.models import ( from request.models import (
Request, TemplateAccessType, LeaseType, TemplateAccessAction, Request, TemplateAccessType, LeaseType, TemplateAccessAction,
ExtendLeaseAction, ResourceChangeAction, DiskResizeAction ExtendLeaseAction, ResourceChangeAction, DiskResizeAction, RequestField
) )
from storage.models import Disk from storage.models import Disk
from vm.models import Instance from vm.models import Instance
...@@ -42,7 +43,39 @@ from request.tables import ( ...@@ -42,7 +43,39 @@ from request.tables import (
from request.forms import ( from request.forms import (
LeaseTypeForm, TemplateAccessTypeForm, TemplateRequestForm, LeaseTypeForm, TemplateAccessTypeForm, TemplateRequestForm,
LeaseRequestForm, ResourceRequestForm, ResizeRequestForm, LeaseRequestForm, ResourceRequestForm, ResizeRequestForm,
RequestFieldModelForm
) )
from django.urls import reverse_lazy
class RequestFieldFormView(LoginRequiredMixin, CreateView):
template_name = 'request/_request-field-form.html'
model = RequestField
form_class = RequestFieldModelForm
success_url = reverse_lazy('request.views.field-list')
class RequestFieldListView(LoginRequiredMixin, ListView):
template_name = 'request/field-list.html'
model = RequestField
def get_context_data(self, *args, **kwargs):
ctx = super(RequestFieldListView, self).get_context_data(*args,
**kwargs)
ctx['add_form'] = RequestFieldModelForm()
ctx['add_form'].helper.form_show_labels = False
return ctx
class RequestFieldDetailView(LoginRequiredMixin, DetailView):
model = RequestField
class RequestFieldDeleteView(LoginRequiredMixin, DeleteView):
model = RequestField
template_name = "dashboard/confirm/base-delete.html"
success_url = reverse_lazy('request.views.field-list')
class RequestList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView): class RequestList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
...@@ -173,7 +206,7 @@ class TemplateRequestView(LoginRequiredMixin, FormView): ...@@ -173,7 +206,7 @@ class TemplateRequestView(LoginRequiredMixin, FormView):
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(TemplateRequestView, self).get_form_kwargs() kwargs = super(TemplateRequestView, self).get_form_kwargs()
kwargs['request'] = self.request kwargs['type'] = 'template'
return kwargs return kwargs
def form_valid(self, form): def form_valid(self, form):
...@@ -187,9 +220,13 @@ class TemplateRequestView(LoginRequiredMixin, FormView): ...@@ -187,9 +220,13 @@ class TemplateRequestView(LoginRequiredMixin, FormView):
) )
ta.save() ta.save()
message = ''
for field in form.get_dynamic_fields():
message += "%s: %s\n" % (unicode(field.label),
unicode(data[field.name]))
req = Request( req = Request(
user=user, user=user,
message=data['message'], message=message,
type=Request.TYPES.template, type=Request.TYPES.template,
action=ta action=ta
) )
...@@ -236,6 +273,12 @@ class LeaseRequestView(VmRequestMixin, FormView): ...@@ -236,6 +273,12 @@ class LeaseRequestView(VmRequestMixin, FormView):
user_level = "operator" user_level = "operator"
success_message = _("Request successfully sent.") success_message = _("Request successfully sent.")
def get_form_kwargs(self):
kwargs = super(LeaseRequestView, self).get_form_kwargs()
kwargs['type'] = 'resource'
return kwargs
def form_valid(self, form): def form_valid(self, form):
data = form.cleaned_data data = form.cleaned_data
user = self.request.user user = self.request.user
...@@ -247,9 +290,14 @@ class LeaseRequestView(VmRequestMixin, FormView): ...@@ -247,9 +290,14 @@ class LeaseRequestView(VmRequestMixin, FormView):
) )
el.save() el.save()
message = ''
for field in form.get_dynamic_fields():
message += "%s: %s\n" % (unicode(field.label),
unicode(data[field.name]))
req = Request( req = Request(
user=user, user=user,
message=data['message'], message=message,
type=Request.TYPES.lease, type=Request.TYPES.lease,
action=el action=el
) )
...@@ -267,6 +315,7 @@ class ResourceRequestView(VmRequestMixin, FormView): ...@@ -267,6 +315,7 @@ class ResourceRequestView(VmRequestMixin, FormView):
def get_form_kwargs(self): def get_form_kwargs(self):
kwargs = super(ResourceRequestView, self).get_form_kwargs() kwargs = super(ResourceRequestView, self).get_form_kwargs()
kwargs['type'] = 'resource'
kwargs['can_edit'] = True kwargs['can_edit'] = True
kwargs['instance'] = self.get_vm() kwargs['instance'] = self.get_vm()
return kwargs return kwargs
...@@ -292,9 +341,14 @@ class ResourceRequestView(VmRequestMixin, FormView): ...@@ -292,9 +341,14 @@ class ResourceRequestView(VmRequestMixin, FormView):
) )
rc.save() rc.save()
message = ''
for field in form.get_dynamic_fields():
message += "%s: %s\n" % (unicode(field.label),
unicode(data[field.name]))
req = Request( req = Request(
user=user, user=user,
message=data['message'], message=message,
type=Request.TYPES.resource, type=Request.TYPES.resource,
action=rc action=rc
) )
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment