Skip to content
Snippets Groups Projects
Commit 88ed4890 authored by Tanner Prestegard's avatar Tanner Prestegard Committed by GraceDB
Browse files

Forms on superevent web display use AJAX to API

Changing signoff form and expose/hide button on the superevent
web display to make an AJAX request to the relevant API endpoints.
This way, we don't have to maintain separate permissions and views
for web resources.
parent 7e357dd2
No related branches found
No related tags found
No related merge requests found
......@@ -933,6 +933,14 @@ class SignoffBase(models.Model):
signoff_type = models.CharField(max_length=3, blank=False,
choices=SIGNOFF_TYPE_CHOICES)
# Timezones for instruments (this should really be handled separately
# by an instrument class)
instrument_time_zones = {
INSTRUMENT_H1: 'America/Los_Angeles',
INSTRUMENT_L1: 'America/Chicago',
INSTRUMENT_V1: 'Europe/Rome',
}
class Meta:
abstract = True
......
......@@ -2,8 +2,7 @@ from django import forms
from django.utils.translation import ugettext_lazy as _
from .models import Superevent, Log, Signoff
from .utils import create_log, create_signoff_for_superevent, \
update_signoff_for_superevent
from .utils import create_log, create_signoff, update_signoff
from core.forms import ModelFormUpdateMixin
from core.vfile import VersionedFile
......@@ -14,42 +13,17 @@ logger = logging.getLogger(__name__)
class SignoffForm(forms.ModelForm):
ACTION_CHOICES = (
('CR', 'create'),
('UP', 'update'),
)
action = forms.fields.ChoiceField(choices=ACTION_CHOICES)
delete = forms.fields.BooleanField(required=False)
class Meta:
model = Signoff
fields = ['status', 'comment', 'signoff_type', 'superevent',
'submitter', 'instrument', 'delete']
fields = ['status', 'comment', 'signoff_type', 'instrument']
def __init__(self, *args, **kwargs):
super(SignoffForm, self).__init__(*args, **kwargs)
# Hide some fields that we will populate either by default
# when we instantiate the form or with the request data
self.fields['signoff_type'].widget = forms.HiddenInput()
self.fields['superevent'].widget = forms.HiddenInput()
self.fields['submitter'].widget = forms.HiddenInput()
self.fields['instrument'].widget = forms.HiddenInput()
self.fields['action'].widget = forms.HiddenInput()
def save(self, *args, **kwargs):
if self.cleaned_data['action'] == 'CR':
signoff = create_signoff_for_superevent(self.instance.superevent,
self.instance.submitter, self.instance.signoff_type,
self.instance.instrument, self.instance.status,
self.instance.comment, add_log_message=True, issue_alert=True)
elif self.cleaned_data['action'] == 'UP':
signoff = update_signoff_for_superevent(self.instance,
self.instance.submitter, self.changed_data,
add_log_message=True, issue_alert=True)
else:
raise Exception('action must be CR (create) or UP (update)')
return signoff
class LogCreateForm(forms.ModelForm):
......
# mixins for class-based views
import pytz
from django import forms
from django.conf import settings
from django.contrib.auth.models import Group as AuthGroup
from django.contrib.auth.models import Permission
from django.contrib.contenttypes.models import ContentType
from django.views.generic.base import ContextMixin
from guardian.models import GroupObjectPermission
from core.time_utils import gpsToUtc
from .forms import SignoffForm
from .models import Signoff
import logging
logger = logging.getLogger(__name__)
......@@ -34,7 +39,7 @@ class OperatorSignoffMixin(ContextMixin):
# Determine if a signoff object already exists
signoff = self.object.signoff_set.filter(instrument=signoff_instrument,
signoff_type='OP').first()
signoff_type=Signoff.SIGNOFF_TYPE_OPERATOR).first()
# Check if label requesting signoff exists
signoff_request_label_name = signoff_instrument + 'OPS'
......@@ -47,20 +52,27 @@ class OperatorSignoffMixin(ContextMixin):
if not signoff_active:
return context
# Get object time in operator timezone
obj_time_for_operator = gpsToUtc(self.object.gpstime).astimezone(
pytz.timezone(Signoff.instrument_time_zones[signoff_instrument]))
# Add more to context
context['object_gpstime_in_operator_tz'] = \
obj_time_for_operator.strftime(settings.GRACE_STRFTIME_FORMAT)
context['operator_signoff_instrument'] = signoff_instrument
context['operator_signoff_type'] = Signoff.SIGNOFF_TYPE_OPERATOR
if signoff:
# Populate form with instance
form = SignoffForm(initial={'action': 'UP'}, instance=signoff)
form = SignoffForm(instance=signoff)
context['operator_signoff_exists'] = True
else:
# Default create form
form = SignoffForm(initial={'signoff_type': 'OP',
'instrument': signoff_instrument, 'action': 'CR'})
form = SignoffForm(initial={
'signoff_type': Signoff.SIGNOFF_TYPE_OPERATOR,
'instrument': signoff_instrument,
})
context['operator_signoff_exists'] = False
# Hide delete checkbox - doesn't apply to creation
form.fields['delete'].widget=forms.HiddenInput()
context['operator_signoff_form'] = form
return context
......@@ -87,7 +99,7 @@ class AdvocateSignoffMixin(ContextMixin):
# Determine if a signoff object already exists
signoff = self.object.signoff_set.filter(instrument=signoff_instrument,
signoff_type='ADV').first()
signoff_type=Signoff.SIGNOFF_TYPE_ADVOCATE).first()
# Check if label requesting signoff exists
signoff_request_label_name = 'ADVREQ'
......@@ -102,66 +114,53 @@ class AdvocateSignoffMixin(ContextMixin):
# Add more to context
context['advocate_signoff_instrument'] = signoff_instrument
context['advocate_signoff_type'] = Signoff.SIGNOFF_TYPE_ADVOCATE
if signoff:
# Populate form with instance
form = SignoffForm(initial={'action': 'UP'}, instance=signoff)
form = SignoffForm(instance=signoff)
context['advocate_signoff_exists'] = True
else:
# Default create form
form = SignoffForm(initial={'signoff_type': 'ADV',
'instrument': signoff_instrument, 'action': 'CR'})
form = SignoffForm(initial={
'signoff_type': Signoff.SIGNOFF_TYPE_ADVOCATE,
'instrument': signoff_instrument,
})
context['advocate_signoff_exists'] = False
# Hide delete checkbox - doesn't apply to creation
form.fields['delete'].widget=forms.HiddenInput()
context['advocate_signoff_form'] = form
return context
class LvemPermissionMixin(ContextMixin):
class ExposeHideMixin(ContextMixin):
expose_perm_name = 'superevents.expose_superevent'
hide_perm_name = 'superevents.hide_superevent'
form_url_view_name = 'shib:default:superevents:superevent-permissions'
def get_context_data(self, **kwargs):
# Get base context
context = super(LvemPermissionMixin, self).get_context_data(**kwargs)
# Get LV-EM observers group
lvem_obs_group = AuthGroup.objects.get(
name=settings.LVEM_OBSERVERS_GROUP)
# Get permission objects
model_name = self.model.__name__.lower()
ctype = ContentType.objects.get(app_label=self.model._meta.app_label,
model=model_name)
p_view = Permission.objects.get(codename='view_{0}'.format(model_name))
p_change = Permission.objects.get(codename='change_{0}'.format(
model_name))
# Determine
lvem_obs_can_view = GroupObjectPermission.objects.filter(
content_type=ctype, object_pk=self.object.pk, group=lvem_obs_group,
permission=p_view).exists()
lvem_obs_can_change = GroupObjectPermission.objects.filter(
content_type=ctype, object_pk=self.object.pk, group=lvem_obs_group,
permission=p_change).exists()
# Determine user permissions for exposing to or protecting from
# the LV-EM observers group
if (lvem_obs_can_view and lvem_obs_can_change and
self.request.user.has_perm(
'guardian.delete_groupobjectpermission')):
perms = False, True
elif (not lvem_obs_can_view and not lvem_obs_can_change and
self.request.user.has_perm(
'guardian.add_groupobjectpermission')):
perms = True, False
else:
perms = False, False
context = super(ExposeHideMixin, self).get_context_data(**kwargs)
# Determine if user can modify permissions to expose or hide
can_modify_permissions = False
if (self.request.user.has_perm(self.expose_perm_name) and
not self.object.is_exposed):
# Object is hidden and user can expose
can_modify_permissions = True
button_text = 'Make this superevent publicly visible'
action = 'expose'
elif (self.request.user.has_perm(self.hide_perm_name) and
self.object.is_exposed):
# Object is visible and user can hide
can_modify_permissions = True
button_text = 'Make this superevent internal-only'
action = 'hide'
# Update context
context['can_expose_to_lvem'] = perms[0]
context['can_protect_from_lvem'] = perms[1]
context['lvem_group_name'] = settings.LVEM_OBSERVERS_GROUP
context['can_modify_permissions'] = can_modify_permissions
if can_modify_permissions:
context['permissions_form_button_text'] = button_text
context['permissions_action'] = action
return context
......@@ -8,15 +8,19 @@ from django.views.generic.detail import DetailView
from django.contrib.auth.models import Group as AuthGroup, Permission
from django.contrib.contenttypes.models import ContentType
from django.contrib import messages
from guardian.models import GroupObjectPermission
from guardian.shortcuts import assign_perm, remove_perm
from .forms import LogCreateForm, SignoffForm
from .mixins import LvemPermissionMixin, OperatorSignoffMixin, \
from .mixins import ExposeHideMixin, OperatorSignoffMixin, \
AdvocateSignoffMixin
from .models import Superevent, Log
from .utils import get_superevent_by_date_id_or_404, \
confirm_superevent_as_gw, delete_signoff_for_superevent
confirm_superevent_as_gw, delete_signoff
from core.permission_utils import expose_event_or_superevent_to_lvem, \
expose_event_or_superevent_to_public
from core.http import check_and_serve_file
from core.vfile import VersionedFile
from events.models import EMGroup
......@@ -29,7 +33,7 @@ logger = logging.getLogger(__name__)
class SupereventDetailView(OperatorSignoffMixin, AdvocateSignoffMixin,
LvemPermissionMixin, DetailView, DisplayFarMixin):
ExposeHideMixin, DetailView, DisplayFarMixin):
model = Superevent
template_name = 'superevents/detail.html'
......@@ -310,12 +314,6 @@ def modify_permissions(request, superevent_id):
except AuthGroup.DoesNotExist:
return HttpResponseNotFound('Group not found')
# Get content type and permissions
ctype = ContentType.objects.get(app_label='superevents',
model='superevent')
p_view = Permission.objects.get(codename='view_superevent')
p_change = Permission.objects.get(codename='change_superevent')
# Make sure the user is authorized.
if action == 'expose':
# Check permissions
......@@ -323,11 +321,8 @@ def modify_permissions(request, superevent_id):
msg = "You aren't authorized to create permission objects."
return HttpResponseForbidden(msg)
# Create GOPs
GroupObjectPermission.objects.get_or_create(content_type=ctype,
group=group, permission=p_view, object_pk=superevent.id)
GroupObjectPermission.objects.get_or_create(content_type=ctype,
group=group, permission=p_change, object_pk=superevent.id)
assign_perm('superevents.view_superevent', group, superevent)
assign_perm('superevents.annotate_superevent', group, superevent)
elif action == 'protect':
# Check permissions
......@@ -336,21 +331,8 @@ def modify_permissions(request, superevent_id):
return HttpResponseForbidden(msg)
# Delete gops
try:
gop = GroupObjectPermission.objects.get(content_type=ctype,
group=group, permission=p_view, object_pk=superevent.id)
gop.delete()
except GroupObjectPermission.DoesNotExist:
# Couldn't find it. Take no action.
pass
try:
gop = GroupObjectPermission.objects.get(content_type=ctype,
group=group, permission=p_change, object_pk=superevent.id)
gop.delete()
except GroupObjectPermission.DoesNotExist:
# Couldn't find it. Take no action.
pass
remove_perm('superevent.view_superevent', group, superevent)
remove_perm('superevent.annotate_superevent', group, superevent)
else:
msg = "Unknown action. Choices are 'expose' and 'protect'."
return HttpResponseBadRequest(msg)
......@@ -408,7 +390,7 @@ def modify_signoff(request, superevent_id):
# Check for delete parameter. If True, just delete the signoff.
if delete:
delete_signoff_for_superevent(signoff, request.user,
delete_signoff(signoff, request.user,
add_log_message=True, issue_alert=True)
messages.info(request, "Signoff deleted.")
return HttpResponseRedirect(original_url)
......
......@@ -59,22 +59,15 @@
</div>
{% endif %}
{# not sure why we need this if statement, maybe can delete in the future #}
{% if 'lvem_view' not in request.path %}
<!-- XXX This next bit is super hacky. -->
{% if can_expose_to_lvem %}
<div class="content-area">
<form action="{% url "superevents:modify-permissions" superevent.superevent_id %}" method="POST">
<input type="hidden" name="group_name" value="{{ lvem_group_name }}">
<input type="hidden" name="action" value="expose">
<input type="submit" value="Expose this superevent to LV-EM" class="permButtonClass">
</form>
</div>
{% elif can_protect_from_lvem %}
{#-- XXX This next bit is super hacky. #}
{% if can_modify_permissions %}
<div class="content-area">
<form action="{% url "superevents:modify-permissions" superevent.superevent_id %}" method="POST">
<input type="hidden" name="group_name" value="{{ lvem_group_name }}">
<input type="hidden" name="action" value="protect">
<input type="submit" value="Revoke LV-EM permissions for this superevent" class="permButtonClass">
<form action="{% url "shib:default:superevents:superevent-permission-modify" superevent.superevent_id %}" method="POST" id="permissions_form">
<input type="hidden" name="action" value="{{ permissions_action }}">
<input type="submit" value="{{ permissions_form_button_text }}" class="permButtonClass">
</form>
</div>
{% endif %}
......@@ -86,28 +79,31 @@
{% if operator_signoff_exists %}
<p>This event has already been signed off on. Use the form below if you wish to edit or delete the record.</p>
{% else %}
<p>This superevent still requires operator signoff. Please answer the following (and optionally enter a comment): At the time of the
{% if operator_signoff_instrument == 'H1' %}
superevent ({{ superevent.t_0|gpsdate_tz:"lho" }}),
{% elif operator_signoff_instrument == 'L1' %}
superevent ({{ superevent.t_0|gpsdate_tz:"llo" }}),
{% elif operator_signoff_instrument == 'V1' %}
superevent ({{ superevent.t_0|gpsdate_tz:"virgo" }}),
{% else %}
superevent,
{% endif %}
was the operating status of the detector basically okay, or not?</p>
<p>This superevent still requires operator signoff. Please answer the following (and optionally enter a comment): At the time of the superevent ({{ object_gpstime_in_operator_tz }}), was the operating status of the detector basically okay, or not?</p>
{% endif %}
<form action="{% url "superevents:modify-signoff" superevent.superevent_id %}" method="POST">
<form class="signoff_form">
<table>
{{ operator_signoff_form.as_table }}
<tr><td></td><td><input type="submit" value="Submit" class="searchButtonClass"></td></tr>
<tr>
<td></td>
<td>
{# inputs are disabled here, enabled by jquery code on page load. Otherwise users who click quickly can activate the form before the jquery is fully loaded #}
{% if operator_signoff_exists %}
{% with operator_signoff_type|add:operator_signoff_instrument as typeinst %}
<input type="submit" formaction="{% url "shib:default:superevents:superevent-signoff-detail" superevent.superevent_id typeinst %}" value="Update signoff" class="searchButtonClass" id="update" disabled>
<input type="submit" formaction="{% url "shib:default:superevents:superevent-signoff-detail" superevent.superevent_id typeinst %}" value="Delete signoff" class="searchButtonClass" id="delete" disabled>
{% endwith %}
{% else %}
<input type="submit" value="Create signoff" class="searchButtonClass" formaction={% url "shib:default:superevents:superevent-signoff-list" superevent.superevent_id %} disabled>
{% endif %}
</td>
</tr>
</table>
</form>
</div>
{% endif %}
<!-- Here is a section for the EM advocate signoffs. -->
{# Here is a section for the EM advocate signoffs. #}
{% if advocate_signoff_authorized and advocate_signoff_active %}
<div class="signoff-area">
<h2>Advocate Signoff</h2>
......@@ -119,10 +115,23 @@
{% endif %}
</p>
<form action="{% url "superevents:modify-signoff" superevent.superevent_id %}" method="POST">
<form class="signoff_form">
<table>
{{ advocate_signoff_form.as_table }}
<tr><td></td><td><input type="submit" value="Submit" class="searchButtonClass"></td></tr>
<tr>
<td></td>
<td>
{# inputs are disabled here, enabled by jquery code on page load. Otherwise users who click quickly can activate the form before the jquery is fully loaded #}
{% if advocate_signoff_exists %}
{% with advocate_signoff_type|add:advocate_signoff_instrument as typeinst %}
<input type="submit" formaction="{% url "shib:default:superevents:superevent-signoff-detail" superevent.superevent_id typeinst %}" value="Update signoff" class="searchButtonClass" id="update" disabled>
<input type="submit" formaction="{% url "shib:default:superevents:superevent-signoff-detail" superevent.superevent_id typeinst %}" value="Delete signoff" class="searchButtonClass" id="delete" disabled>
{% endwith %}
{% else %}
<input type="submit" value="Create signoff" class="searchButtonClass" formaction={% url "shib:default:superevents:superevent-signoff-list" superevent.superevent_id %} disabled>
{% endif %}
</td>
</tr>
</table>
</form>
</div>
......
......@@ -224,6 +224,72 @@ require([
Save, Preview, ScrollPane, Uploader) {
parser.parse();
// We don't enable the input buttons until right now otherwise fast users
// can trigger the form before the javascript is ready... not ideal
$(".signoff_form input[type=submit]").attr('disabled', false);
// Signoff form - determine HTTP method type and url based on
// attributes of button which was pressed. Then submit to
// API url and reload
$(".signoff_form").submit(function(e) {
e.preventDefault();
// Get HTTP method from button used to submit form
var submit_button = $(this).children("input[type=submit][clicked=true]");
var button_id = submit_button.attr("id");
var http_method = 'POST';
if (button_id == 'update') {
http_method = 'PATCH';
} else if (button_id == 'delete') {
http_method = 'DELETE';
}
// Get URL from button used to submit form; otherwise use
// main form action
var url = submit_button.attr('formaction');
if (url === undefined) {
url = $(this).attr('action');
}
// Get and disable all submit buttons on the form
var all_submit_buttons = $(this).children('input[type=submit]');
all_submit_buttons.attr("disabled", true);
// Make ajax request
$.ajax({
type: http_method,
url: url,
data: $(this).serialize(),
success: function(resp) {
// Don't need to re-enable since we reload the page
//all_submit_buttons.attr("disabled", false);
location.reload(true);
},
error: function(error) {
//this.button.set("disabled", false);
var err_msg = "Error " + error.status + ": ";
if (error.responseText != "") {
err_msg += error.responseText;
} else {
err_msg += error.statusText;
}
if (error.status == 404) {
err_msg += ". Reload the page.";
}
alert(err_msg);
// Re-enable all submit buttons
all_submit_buttons.attr("disabled", false);
}
});
});
// Handles determination of which button was used to submit form
$(".signoff_form input[type=submit]").click(function() {
$("input[type=submit]", $(this).parents("form")).removeAttr("clicked");
$(this).attr("clicked", true);
});
//----------------------------------------------------------------------------------------
// Some utility functions
//----------------------------------------------------------------------------------------
......@@ -1169,6 +1235,25 @@ require([
var i3 = put(f, 'input[id="hidden_tagname"][name="tagname"][type="hidden"]');
put(upload_div, 'br');
// Permissions form - submit to URL and reload
$("#permissions_form").submit(function(e) {
e.preventDefault();
$.ajax({
type: 'POST',
url: $(this).attr('action'),
data: $(this).serialize(),
success: function(resp) {
//this.button.set("disabled", false);
location.reload(true);
},
error: function(error) {
//this.button.set("disabled", false);
alert(error);
}
});
});
$("#emo_submit_form").submit(function(e) {
e.preventDefault();
$.ajax({
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment