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 @@
# with CIRCLE. If not, see <http://www.gnu.org/licenses/>.
from django.forms import (
ModelForm, ModelChoiceField, ChoiceField, Form, CharField, RadioSelect,
Textarea, ValidationError
Textarea, ValidationError, TextInput, IntegerField, EmailField
)
from django.utils.translation import ugettext_lazy as _
from django.template.loader import render_to_string
......@@ -27,11 +27,78 @@ from crispy_forms.helper import FormHelper
from crispy_forms.layout import Submit
from request.models import (
LeaseType, TemplateAccessType, TemplateAccessAction,
LeaseType, TemplateAccessType, TemplateAccessAction, RequestField
)
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):
@property
def helper(self):
......@@ -80,27 +147,18 @@ class InitialFromFileMixin(object):
return message.strip()
class TemplateRequestForm(InitialFromFileMixin, Form):
message = CharField(widget=Textarea, label=_("Message"))
class TemplateRequestForm(EditableForm):
template = ModelChoiceField(TemplateAccessType.objects.all(),
label=_("Template share"))
level = ChoiceField(TemplateAccessAction.LEVELS, widget=RadioSelect,
initial=TemplateAccessAction.LEVELS.user)
initial_template = "request/initials/template.html"
class LeaseRequestForm(InitialFromFileMixin, Form):
class LeaseRequestForm(EditableForm):
lease = ModelChoiceField(LeaseType.objects.all(), label=_("Lease"))
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/lease.html"
class ResourceRequestForm(InitialFromFileMixin, VmResourcesForm):
message = CharField(widget=Textarea, label=_("Message"))
initial_template = "request/initials/resources.html"
class ResourceRequestForm(EditableForm, VmResourcesForm):
def clean(self):
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
from django.db.models import (
Model, CharField, IntegerField, TextField, ForeignKey, ManyToManyField,
BooleanField,
)
from django.db.models.signals import post_save
from django.conf import settings
......@@ -157,6 +158,27 @@ class Request(TimeStampedModel):
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):
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 @@
{% csrf_token %}
{{ form.lease|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"/>
</form>
......@@ -21,6 +21,8 @@
</label>
</div>
{% 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"/>
</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 @@
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">
<a class="btn btn-xs btn-primary pull-right "href="{% url "request.views.type-list" %}">
<div class="pull-right">
<a class="btn btn-xs btn-primary"href="{% url "request.views.type-list" %}">
{% 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>
</div>
<div class="panel-body">
......
......@@ -24,7 +24,9 @@
</div>
{% 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 %}
{{ form.message|as_crispy_field }}
{% for field in form.get_dynamic_fields %}
{{ field|as_crispy_field }}
{% endfor %}
<button type="submit" class="btn btn-success">
{% trans "Request new resources" %}
</button>
......
......@@ -25,7 +25,7 @@ from mock import Mock, patch
from common.tests.celery_mock import MockCeleryMixin
from vm.models import Instance, InstanceTemplate, Lease
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 vm.operations import ResourcesOperation
......@@ -67,12 +67,16 @@ class RequestTest(LoginMixin, MockCeleryMixin, TestCase):
inst = Instance.objects.get(pk=1)
inst.set_level(self.u1, 'owner')
field = RequestField(fieldname="Oka", type="Char",
request_type="resource", required=True)
field.save()
req_count = Request.objects.count()
resp = c.post("/request/resource/1/", {
'num_cores': 5,
'ram_size': 512,
'priority': 30,
'message': "szia",
'field1': "szia",
})
self.assertEqual(resp.status_code, 302)
self.assertEqual(req_count + 1, Request.objects.count())
......@@ -104,17 +108,25 @@ class RequestTest(LoginMixin, MockCeleryMixin, TestCase):
template = InstanceTemplate.objects.get(pk=1)
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()
resp = c.post("/request/template/", {
'template': 1,
'level': "user",
'message': "szia",
'field1': "IIT",
'field2': 10,
})
self.assertEqual(resp.status_code, 302)
self.assertEqual(req_count + 1, Request.objects.count())
new_request = Request.objects.latest("pk")
self.assertEqual(new_request.status, "PENDING")
self.assertEqual(new_request.message, "Tanszek: IIT\nSzobaszam: 10\n")
new_request.accept(self.us)
new_request = Request.objects.latest("pk")
......
......@@ -24,6 +24,8 @@ from .views import (
TemplateAccessTypeCreate, TemplateAccessTypeDetail,
TemplateRequestView, LeaseRequestView, ResourceRequestView,
LeaseTypeDelete, TemplateAccessTypeDelete, ResizeRequestView,
RequestFieldFormView, RequestFieldListView, RequestFieldDetailView,
RequestFieldDeleteView,
)
urlpatterns = [
......@@ -35,6 +37,15 @@ urlpatterns = [
url(r'^type/list/$', RequestTypeList.as_view(),
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
url(r'^type/lease/create/$', LeaseTypeCreate.as_view(),
name="request.views.lease-type-create"),
......
......@@ -18,6 +18,7 @@ from __future__ import unicode_literals, absolute_import
from django.views.generic import (
UpdateView, TemplateView, DetailView, CreateView, FormView, DeleteView,
ListView
)
from django.contrib import messages
from django.contrib.messages.views import SuccessMessageMixin
......@@ -32,7 +33,7 @@ from django_tables2 import SingleTableView
from request.models import (
Request, TemplateAccessType, LeaseType, TemplateAccessAction,
ExtendLeaseAction, ResourceChangeAction, DiskResizeAction
ExtendLeaseAction, ResourceChangeAction, DiskResizeAction, RequestField
)
from storage.models import Disk
from vm.models import Instance
......@@ -42,7 +43,39 @@ from request.tables import (
from request.forms import (
LeaseTypeForm, TemplateAccessTypeForm, TemplateRequestForm,
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):
......@@ -173,7 +206,7 @@ class TemplateRequestView(LoginRequiredMixin, FormView):
def get_form_kwargs(self):
kwargs = super(TemplateRequestView, self).get_form_kwargs()
kwargs['request'] = self.request
kwargs['type'] = 'template'
return kwargs
def form_valid(self, form):
......@@ -187,9 +220,13 @@ class TemplateRequestView(LoginRequiredMixin, FormView):
)
ta.save()
message = ''
for field in form.get_dynamic_fields():
message += "%s: %s\n" % (unicode(field.label),
unicode(data[field.name]))
req = Request(
user=user,
message=data['message'],
message=message,
type=Request.TYPES.template,
action=ta
)
......@@ -236,6 +273,12 @@ class LeaseRequestView(VmRequestMixin, FormView):
user_level = "operator"
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):
data = form.cleaned_data
user = self.request.user
......@@ -247,9 +290,14 @@ class LeaseRequestView(VmRequestMixin, FormView):
)
el.save()
message = ''
for field in form.get_dynamic_fields():
message += "%s: %s\n" % (unicode(field.label),
unicode(data[field.name]))
req = Request(
user=user,
message=data['message'],
message=message,
type=Request.TYPES.lease,
action=el
)
......@@ -267,6 +315,7 @@ class ResourceRequestView(VmRequestMixin, FormView):
def get_form_kwargs(self):
kwargs = super(ResourceRequestView, self).get_form_kwargs()
kwargs['type'] = 'resource'
kwargs['can_edit'] = True
kwargs['instance'] = self.get_vm()
return kwargs
......@@ -292,9 +341,14 @@ class ResourceRequestView(VmRequestMixin, FormView):
)
rc.save()
message = ''
for field in form.get_dynamic_fields():
message += "%s: %s\n" % (unicode(field.label),
unicode(data[field.name]))
req = Request(
user=user,
message=data['message'],
message=message,
type=Request.TYPES.resource,
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