diff --git a/gracedb/events/models.py b/gracedb/events/models.py
index 92770429a4953867d6136555e2c6fdceaac4d900..a769a07eb3272fe17f0bd53230fa58dc53ba1a07 100644
--- a/gracedb/events/models.py
+++ b/gracedb/events/models.py
@@ -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
 
diff --git a/gracedb/superevents/forms.py b/gracedb/superevents/forms.py
index af10d2052bee686a0aedf7ce772415a85990a0ac..e32c01a2c95f95c650773905feaacbb86cc64568 100644
--- a/gracedb/superevents/forms.py
+++ b/gracedb/superevents/forms.py
@@ -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):
diff --git a/gracedb/superevents/mixins.py b/gracedb/superevents/mixins.py
index 639222896e34c65e8475bf2dec7e2586638742db..b543e7bc22bc339ff61afd43a5e9e8e034c84e62 100644
--- a/gracedb/superevents/mixins.py
+++ b/gracedb/superevents/mixins.py
@@ -1,13 +1,18 @@
 # 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
diff --git a/gracedb/superevents/views.py b/gracedb/superevents/views.py
index 2efbc6070b31138dfc6d90a2e44201f3dd766119..607b88cf6ec64bd16dae43cebb23d6beb856bd7b 100644
--- a/gracedb/superevents/views.py
+++ b/gracedb/superevents/views.py
@@ -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)
diff --git a/gracedb/templates/superevents/detail.html b/gracedb/templates/superevents/detail.html
index 1f8c3befe2cdf51f4a0d9d0173a8f0a2d28876e8..28c2feaf43fda6dd1316ce4dfe264323d8bb6d50 100644
--- a/gracedb/templates/superevents/detail.html
+++ b/gracedb/templates/superevents/detail.html
@@ -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>
diff --git a/gracedb/templates/superevents/superevent_detail_script.js b/gracedb/templates/superevents/superevent_detail_script.js
index 89320153f7d10d6f78738484dcb6c4b031aa5467..4b4feae0d436021b72777ace0e9c9b7ee5b62776 100644
--- a/gracedb/templates/superevents/superevent_detail_script.js
+++ b/gracedb/templates/superevents/superevent_detail_script.js
@@ -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({