Commit 9cf54290 by Kálmán Viktor

request: add reason for decline

parent 9915ef55
...@@ -1296,3 +1296,14 @@ textarea[name="new_members"] { ...@@ -1296,3 +1296,14 @@ textarea[name="new_members"] {
text-align: center; text-align: center;
} }
} }
#request-buttons {
form {
display: inline;
}
textarea {
resize: none;
min-height: 80px;
}
}
...@@ -26,8 +26,8 @@ ...@@ -26,8 +26,8 @@
{% trans "Changing resources is only possible on virtual machines with STOPPED state. We suggest to turn off the VM after submitting the request otherwise it will be automatically stopped in the future when the request is accepted." %} {% trans "Changing resources is only possible on virtual machines with STOPPED state. We suggest to turn off the VM after submitting the request otherwise it will be automatically stopped in the future when the request is accepted." %}
</div> </div>
<div class="form-group"> <div class="form-group">
<label>{% trans "Reason" %}*</label> <label>{% trans "Message" %}*</label>
<textarea class="form-control" name="reason">{% include "request/initials/resources.html" %}</textarea> <textarea class="form-control" name="message">{% include "request/initials/resources.html" %}</textarea>
</div> </div>
<input type="submit" class="btn btn-success btn-sm"/> <input type="submit" class="btn btn-success btn-sm"/>
</div> </div>
......
...@@ -63,7 +63,7 @@ class InitialFromFileMixin(object): ...@@ -63,7 +63,7 @@ class InitialFromFileMixin(object):
request = kwargs.pop("request", None) request = kwargs.pop("request", None)
super(InitialFromFileMixin, self).__init__(*args, **kwargs) super(InitialFromFileMixin, self).__init__(*args, **kwargs)
self.initial['reason'] = render_to_string( self.initial['message'] = render_to_string(
self.initial_template, self.initial_template,
RequestContext(request, {}), RequestContext(request, {}),
) )
...@@ -74,17 +74,17 @@ class TemplateRequestForm(InitialFromFileMixin, Form): ...@@ -74,17 +74,17 @@ class TemplateRequestForm(InitialFromFileMixin, Form):
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)
reason = CharField(widget=forms.Textarea, label=_("Reason")) message = CharField(widget=forms.Textarea, label=_("Message"))
initial_template = "request/initials/template.html" initial_template = "request/initials/template.html"
class LeaseRequestForm(InitialFromFileMixin, Form): class LeaseRequestForm(InitialFromFileMixin, Form):
lease = ModelChoiceField(LeaseType.objects.all(), label=_("Lease")) lease = ModelChoiceField(LeaseType.objects.all(), label=_("Lease"))
reason = CharField(widget=forms.Textarea) message = CharField(widget=forms.Textarea)
initial_template = "request/initials/lease.html" initial_template = "request/initials/lease.html"
class ResourceRequestForm(VmResourcesForm): class ResourceRequestForm(VmResourcesForm):
reason = CharField(widget=forms.Textarea) message = CharField(widget=forms.Textarea)
...@@ -46,12 +46,14 @@ class Migration(migrations.Migration): ...@@ -46,12 +46,14 @@ class Migration(migrations.Migration):
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)), ('created', model_utils.fields.AutoCreatedField(default=django.utils.timezone.now, verbose_name='created', editable=False)),
('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)), ('modified', model_utils.fields.AutoLastModifiedField(default=django.utils.timezone.now, verbose_name='modified', editable=False)),
('status', models.CharField(default=b'UNSEEN', max_length=10, choices=[(b'UNSEEN', 'unseen'), (b'PENDING', 'pending'), (b'ACCEPTED', 'accepted'), (b'DECLINED', 'declined')])), ('status', models.CharField(default=b'PENDING', max_length=10, choices=[(b'PENDING', 'pending'), (b'ACCEPTED', 'accepted'), (b'DECLINED', 'declined')])),
('type', models.CharField(max_length=10, choices=[(b'resource', 'resource request'), (b'lease', 'lease request'), (b'template', 'template access')])), ('type', models.CharField(max_length=10, choices=[(b'resource', 'resource request'), (b'lease', 'lease request'), (b'template', 'template access')])),
('reason', models.TextField(help_text=b'szia')), ('message', models.TextField(verbose_name='Message')),
('reason', models.TextField(verbose_name='Reason')),
('object_id', models.IntegerField()), ('object_id', models.IntegerField()),
('closed_by', models.ForeignKey(related_name='closed_by', to=settings.AUTH_USER_MODEL, null=True)),
('content_type', models.ForeignKey(to='contenttypes.ContentType')), ('content_type', models.ForeignKey(to='contenttypes.ContentType')),
('user', models.ForeignKey(verbose_name='user', to=settings.AUTH_USER_MODEL)), ('user', models.ForeignKey(related_name='user', to=settings.AUTH_USER_MODEL)),
], ],
options={ options={
'abstract': False, 'abstract': False,
......
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
from django.conf import settings
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('request', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='request',
name='closed_by',
field=models.ForeignKey(related_name='closed_by', to=settings.AUTH_USER_MODEL, null=True),
preserve_default=True,
),
migrations.AlterField(
model_name='request',
name='user',
field=models.ForeignKey(related_name='user', to=settings.AUTH_USER_MODEL),
preserve_default=True,
),
]
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import models, migrations
class Migration(migrations.Migration):
dependencies = [
('request', '0002_auto_20150317_1211'),
]
operations = [
migrations.AlterField(
model_name='request',
name='reason',
field=models.TextField(verbose_name='Reason'),
preserve_default=True,
),
]
...@@ -52,12 +52,11 @@ class RequestType(Model): ...@@ -52,12 +52,11 @@ class RequestType(Model):
class Request(TimeStampedModel): class Request(TimeStampedModel):
STATUSES = Choices( STATUSES = Choices(
('UNSEEN', _("unseen")),
('PENDING', _('pending')), ('PENDING', _('pending')),
('ACCEPTED', _('accepted')), ('ACCEPTED', _('accepted')),
('DECLINED', _('declined')), ('DECLINED', _('declined')),
) )
status = CharField(choices=STATUSES, default=STATUSES.UNSEEN, status = CharField(choices=STATUSES, default=STATUSES.PENDING,
max_length=10) max_length=10)
user = ForeignKey(User, related_name="user") user = ForeignKey(User, related_name="user")
closed_by = ForeignKey(User, related_name="closed_by", null=True) closed_by = ForeignKey(User, related_name="closed_by", null=True)
...@@ -67,6 +66,7 @@ class Request(TimeStampedModel): ...@@ -67,6 +66,7 @@ class Request(TimeStampedModel):
('template', _("template access")), ('template', _("template access")),
) )
type = CharField(choices=TYPES, max_length=10) type = CharField(choices=TYPES, max_length=10)
message = TextField(verbose_name=_("Message"))
reason = TextField(verbose_name=_("Reason")) reason = TextField(verbose_name=_("Reason"))
content_type = ForeignKey(ContentType) content_type = ForeignKey(ContentType)
...@@ -91,7 +91,6 @@ class Request(TimeStampedModel): ...@@ -91,7 +91,6 @@ class Request(TimeStampedModel):
def get_effect(self): def get_effect(self):
return { return {
"UNSEEN": "primary",
"PENDING": "warning", "PENDING": "warning",
"ACCEPTED": "success", "ACCEPTED": "success",
"DECLINED": "danger", "DECLINED": "danger",
...@@ -99,7 +98,6 @@ class Request(TimeStampedModel): ...@@ -99,7 +98,6 @@ class Request(TimeStampedModel):
def get_status_icon(self): def get_status_icon(self):
return { return {
"UNSEEN": "eye-slash",
"PENDING": "exclamation-triangle", "PENDING": "exclamation-triangle",
"ACCEPTED": "check", "ACCEPTED": "check",
"DECLINED": "times", "DECLINED": "times",
...@@ -111,9 +109,10 @@ class Request(TimeStampedModel): ...@@ -111,9 +109,10 @@ class Request(TimeStampedModel):
self.closed_by = user self.closed_by = user
self.save() self.save()
def decline(self, user): def decline(self, user, reason):
self.status = "DECLINED" self.status = "DECLINED"
self.closed_by = user self.closed_by = user
self.reason = reason
self.save() self.save()
...@@ -202,5 +201,10 @@ def send_notification_to_superusers(sender, instance, created, **kwargs): ...@@ -202,5 +201,10 @@ def send_notification_to_superusers(sender, instance, created, **kwargs):
ugettext_noop("New %(request_type)s"), notification_msg, context ugettext_noop("New %(request_type)s"), notification_msg, context
) )
instance.user.profile.notify(
ugettext_noop("Request submitted"),
ugettext_noop('You can view the request\'s status at this '
'<a href="%(request_url)s">link</a>.'), context
)
post_save.connect(send_notification_to_superusers, sender=Request) post_save.connect(send_notification_to_superusers, sender=Request)
...@@ -5,6 +5,6 @@ ...@@ -5,6 +5,6 @@
{% include "display-form-errors.html" %} {% include "display-form-errors.html" %}
{% csrf_token %} {% csrf_token %}
{{ form.lease|as_crispy_field }} {{ form.lease|as_crispy_field }}
{{ form.reason|as_crispy_field }} {{ form.message|as_crispy_field }}
<input type="submit" class="btn btn-primary"/> <input type="submit" class="btn btn-primary"/>
</form> </form>
...@@ -21,6 +21,6 @@ ...@@ -21,6 +21,6 @@
</label> </label>
</div> </div>
{% endfor %} {% endfor %}
{{ form.reason|as_crispy_field }} {{ form.message|as_crispy_field }}
<input type="submit" class="btn btn-primary"/> <input type="submit" class="btn btn-primary"/>
</form> </form>
...@@ -12,9 +12,11 @@ ...@@ -12,9 +12,11 @@
<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 href="{% url "request.views.request-list" %}" class="btn btn-default btn-xs pull-right"> {% if request.user.is_superuser %}
{% trans "Back" %} <a href="{% url "request.views.request-list" %}" class="btn btn-default btn-xs pull-right">
</a> {% trans "Back" %}
</a>
{% endif %}
<h3 class="no-margin"> <h3 class="no-margin">
<i class="fa fa-{{ object.get_request_icon }}"></i> <i class="fa fa-{{ object.get_request_icon }}"></i>
{{ object.get_readable_type|capfirst }} {{ object.get_readable_type|capfirst }}
...@@ -32,7 +34,7 @@ ...@@ -32,7 +34,7 @@
</a> </a>
</p> </p>
<p> <p>
<pre>{{ object.reason }}</pre> <pre>{{ object.message }}</pre>
</p> </p>
<hr /> <hr />
{% if object.type == "lease" %} {% if object.type == "lease" %}
...@@ -87,36 +89,44 @@ ...@@ -87,36 +89,44 @@
hacks!!! hacks!!!
{% endif %} {% endif %}
{% if object.status == "PENDING" %} {% if object.status == "PENDING" and request.user.is_superuser %}
<hr /> <hr />
<div class="pull-right"> <div class="pull-right" id="request-buttons">
<form method="POST" style="display: inline;"> <form method="POST">
{% csrf_token %} {% csrf_token %}
<p>
<textarea class="form-control" placeholder="{% trans "Reason (sent to the user if the request is declined)" %}" name="reason"></textarea>
</p>
<button class="btn btn-danger" type="submit"> <button class="btn btn-danger" type="submit">
<i class="fa fa-thumbs-down"></i> <i class="fa fa-thumbs-down"></i>
{% trans "Decline" %} {% trans "Decline" %}
</button> </button>
</form> </form>
{{ acceptable_statuses }}
{% if object.type == "resource" and action.instance.status not in accept_states %} {% if object.type == "resource" and action.instance.status not in accept_states %}
{% trans "You can't accept this request because of the VM's state." %} {% trans "You can't accept this request because of the VM's state." %}
{% else %} {% else %}
<form method="POST" style="display: inline;"> <form method="POST">
{% csrf_token %} {% csrf_token %}
<input type="hidden" name="accept" value="1"/> <input type="hidden" name="accept" value="1"/>
<button class="btn btn-success"> <button class="btn btn-success">
<i class="fa fa-thumbs-up"></i> <i class="fa fa-thumbs-up"></i>
{% trans "Accept" %} {% trans "Accept" %}
</button> </button>
</form> </form>
{% endif %} {% endif %}
</div> </div>
{% else %} {% endif %}
{% if object.status != "PENDING" %}
<div class="text-right"> <div class="text-right">
{% blocktrans with closed=object.modified|arrowfilter:LANGUAGE_CODE user=object.closed_by.profile.get_display_name %} {% blocktrans with closed=object.modified|arrowfilter:LANGUAGE_CODE user=object.closed_by.profile.get_display_name %}
Closed {{ closed }} by <a href="{{ user.profile.get_absolute_url }}">{{ user }}</a> Closed {{ closed }} by <a href="{{ user.profile.get_absolute_url }}">{{ user }}</a>
{% endblocktrans %} {% endblocktrans %}
{% if object.status == "DECLINED" %}
<p>
<strong>{% trans "Reason" %}:</strong> {{ object.reason }}
</p>
{% endif %}
</div> </div>
{% endif %} {% endif %}
</div><!-- .panel-body --> </div><!-- .panel-body -->
......
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
</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.reason|as_crispy_field }} {{ form.message|as_crispy_field }}
<button type="submit" class="btn btn-success"> <button type="submit" class="btn btn-success">
{% trans "Request new resources" %} {% trans "Request new resources" %}
</button> </button>
......
...@@ -20,7 +20,7 @@ from django.views.generic import ( ...@@ -20,7 +20,7 @@ from django.views.generic import (
UpdateView, TemplateView, DetailView, CreateView, FormView, UpdateView, TemplateView, DetailView, CreateView, FormView,
) )
from django.shortcuts import redirect, get_object_or_404 from django.shortcuts import redirect, get_object_or_404
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied, SuspiciousOperation
from braces.views import SuperuserRequiredMixin, LoginRequiredMixin from braces.views import SuperuserRequiredMixin, LoginRequiredMixin
from django_tables2 import SingleTableView from django_tables2 import SingleTableView
...@@ -59,32 +59,39 @@ class RequestList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView): ...@@ -59,32 +59,39 @@ class RequestList(LoginRequiredMixin, SuperuserRequiredMixin, SingleTableView):
return data return data
class RequestDetail(LoginRequiredMixin, SuperuserRequiredMixin, DetailView): class RequestDetail(LoginRequiredMixin, DetailView):
model = Request model = Request
template_name = "request/detail.html" template_name = "request/detail.html"
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
if self.get_object().status in ["PENDING", "UNSEEN"]: user = self.request.user
user = self.request.user request = self.get_object() # not self.request!
if not user.is_superuser:
raise SuspiciousOperation
if self.get_object().status == "PENDING":
accept = self.request.POST.get("accept") accept = self.request.POST.get("accept")
request = self.get_object() # not self.request! reason = self.request.POST.get("reason")
if accept: if accept:
request.accept(user) request.accept(user)
else: else:
request.decline(user) request.decline(user, reason)
return redirect(request.get_absolute_url()) return redirect(request.get_absolute_url())
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
request = self.object request = self.object
user = self.request.user
if not user.is_superuser and request.user != user:
raise SuspiciousOperation
context = super(RequestDetail, self).get_context_data(**kwargs) context = super(RequestDetail, self).get_context_data(**kwargs)
context['action'] = request.action context['action'] = request.action
context['accept_states'] = ResourcesRequestOperation.accept_states context['accept_states'] = ResourcesRequestOperation.accept_states
if request.status == Request.STATUSES.UNSEEN:
request.status = Request.STATUSES.PENDING
request.save()
return context return context
...@@ -151,7 +158,7 @@ class TemplateRequestView(FormView): ...@@ -151,7 +158,7 @@ class TemplateRequestView(FormView):
req = Request( req = Request(
user=user, user=user,
reason=data['reason'], message=data['message'],
type=Request.TYPES.template, type=Request.TYPES.template,
action=ta action=ta
) )
...@@ -203,7 +210,7 @@ class LeaseRequestView(VmRequestMixin, FormView): ...@@ -203,7 +210,7 @@ class LeaseRequestView(VmRequestMixin, FormView):
req = Request( req = Request(
user=user, user=user,
reason=data['reason'], message=data['message'],
type=Request.TYPES.lease, type=Request.TYPES.lease,
action=el action=el
) )
...@@ -245,7 +252,7 @@ class ResourceRequestView(VmRequestMixin, FormView): ...@@ -245,7 +252,7 @@ class ResourceRequestView(VmRequestMixin, FormView):
req = Request( req = Request(
user=user, user=user,
reason=data['reason'], message=data['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