From 08d532e21e7ef4d4178144925603ac7e7f1e84db Mon Sep 17 00:00:00 2001
From: Alexander Pace <alexander.pace@ligo.org>
Date: Tue, 14 Jan 2025 13:46:36 -0600
Subject: [PATCH 1/5] add some permission management helper functions

only been a decade, might as well stop copying and pasting
---
 gracedb/ligoauth/utils.py | 95 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 95 insertions(+)

diff --git a/gracedb/ligoauth/utils.py b/gracedb/ligoauth/utils.py
index f3626ddcd..4b5e8bcb3 100644
--- a/gracedb/ligoauth/utils.py
+++ b/gracedb/ligoauth/utils.py
@@ -1,7 +1,12 @@
 from django.conf import settings
+from django.contrib.auth.models import User, Permission
+from django.contrib.contenttypes.models import ContentType
 from django.http import HttpResponseForbidden
 from django.utils.functional import wraps
 
+from guardian.models import UserObjectPermission
+from events.models import Pipeline
+
 
 def groups_allowed(group_names):
     """
@@ -28,3 +33,93 @@ def groups_allowed(group_names):
 
 def is_internal(user):
     return user.groups.filter(name=settings.LVC_GROUP).exists()
+
+
+def add_user_to_pipeline_uploaders(user, pipeline):
+    """
+    Function that creates a UserObjectPermission object to upload
+    to a pipeline.
+
+    Inputs:
+        user: a django.contrib.auth.model.User object
+        pipeline: a event.models.Pipeline object
+    """
+
+    # Check to see that the inputs are valid:
+    if not isinstance(user, User):
+        raise ValueError(f'{user} is not a valid User object')
+    if not isinstance(pipeline, Pipeline):
+        raise ValueError(f'{pipeline} is not a valid Pipeline object')
+
+    # Retrieve the objects we will need
+    p = Permission.objects.get(codename='populate_pipeline')
+    ctype = ContentType.objects.get(app_label='events', model='pipeline')
+
+    perm, created = UserObjectPermission.objects.get_or_create(
+                        user=user, permission=p, content_type=ctype,
+                        object_pk=pipeline.pk)
+
+    if created:
+        print(f'Added populate_pipeline permission for {user.username} and',
+              f'{pipeline.name})')
+    else:
+        print(f'{user.username} already has permission to populate {pipeline.name}')
+
+
+def remove_user_from_pipeline_uploaders(user, pipeline):
+    """
+    Function that removes UserObjectPermission object to upload
+    to a pipeline.
+
+    Inputs:
+        user: a django.contrib.auth.model.User object
+        pipeline: a event.models.Pipeline object
+    """
+
+    # Check to see that the inputs are valid:
+    if not isinstance(user, User):
+        raise ValueError(f'{user} is not a valid User object')
+    if not isinstance(pipeline, Pipeline):
+        raise ValueError(f'{pipeline} is not a valid Pipeline object')
+
+    # Retrieve the objects we will need
+    p = Permission.objects.get(codename='populate_pipeline')
+    ctype = ContentType.objects.get(app_label='events', model='pipeline')
+
+    try:
+        perm = UserObjectPermission.objects.get(
+                   user=user, permission=p, content_type=ctype,
+                   object_pk=pipeline.pk)
+    except UserObjectPermission.DoesNotExist:
+        print(f'{user.username} does not have permission to populate',
+              f'{pipeline.name}')
+        return
+
+    perm.delete()
+    print(f'{user.username} removed from {pipeline.name} uploaders')
+
+
+def get_pipeline_uploaders(pipeline):
+    """
+    Function that returns a queryset of user objects that currently have
+    permission to populate a given pipeline
+
+    Inputs:
+        pipeline: a event.models.Pipeline object
+    """
+
+    if not isinstance(pipeline, Pipeline):
+        raise ValueError(f'{pipeline} is not a valid Pipeline object')
+
+    # Retrieve the objects we will need
+    p = Permission.objects.get(codename='populate_pipeline')
+    ctype = ContentType.objects.get(app_label='events', model='pipeline')
+
+    uploaders = UserObjectPermission.objects.filter(
+                    permission=p, content_type=ctype, object_pk=pipeline.pk)
+
+    if uploaders.exists():
+        return User.objects.filter(id__in=uploaders.filter(user_id__isnull=False).\
+                   values_list('user_id', flat=True))
+    else:
+        return User.objects.none()
-- 
GitLab


From d15f6117bc73eed92d1a9c17986774420f9ebf59 Mon Sep 17 00:00:00 2001
From: Alexander Pace <alexander.pace@ligo.org>
Date: Tue, 14 Jan 2025 14:22:22 -0600
Subject: [PATCH 2/5] add SGNL pipeline and associated coinc machinery

---
 config/settings/base.py                       |  1 +
 docs/user_docs/source/models.rst              |  2 +-
 .../migrations/0102_add_sgnl_pipeline.py      | 34 +++++++++++++++++++
 gracedb/events/translator.py                  |  2 +-
 gracedb/events/view_logic.py                  |  5 ++-
 5 files changed, 39 insertions(+), 5 deletions(-)
 create mode 100644 gracedb/events/migrations/0102_add_sgnl_pipeline.py

diff --git a/config/settings/base.py b/config/settings/base.py
index 2818bb2e8..28fa0c49f 100644
--- a/config/settings/base.py
+++ b/config/settings/base.py
@@ -255,6 +255,7 @@ COINC_PIPELINES = [
                     'pycbc',
                     'MBTA',
                     'PyGRB',
+                    'SGNL',
                    ]
 GRB_PIPELINES = [
                     'Fermi',
diff --git a/docs/user_docs/source/models.rst b/docs/user_docs/source/models.rst
index d7de02d37..e3741b660 100644
--- a/docs/user_docs/source/models.rst
+++ b/docs/user_docs/source/models.rst
@@ -12,7 +12,7 @@ The different types of events in GraceDB are distinguished by the following para
 - ``Group``: the working group responsible for finding the candidate
     - values: ``CBC``, ``Burst``, ``Detchar``, ``External``, ``Test`` 
 - ``Pipeline``: the data analysis software tool used make the detection 
-    - values: ``CWB2G``, ``spiir``, ``HardwareInjection``, ``X``, ``Q``, ``Omega``, ``Ringdown``, ``Fermi``, ``Swift``, ``CWB``, ``SNEWS``, ``oLIB``, ``pycbc``, ``INTEGRAL``, ``AGILE``, ``gstlal``, ``MLy``, ``MBTAOnline``, ``MBTA``, ``CHIME``, ``PyGRB``, ``aframe``, ``SVOM``, ``IceCube``, ``GWAK``
+    - values: ``CWB2G``, ``spiir``, ``HardwareInjection``, ``X``, ``Q``, ``Omega``, ``Ringdown``, ``Fermi``, ``Swift``, ``CWB``, ``SNEWS``, ``oLIB``, ``pycbc``, ``INTEGRAL``, ``AGILE``, ``gstlal``, ``MLy``, ``MBTAOnline``, ``MBTA``, ``CHIME``, ``PyGRB``, ``aframe``, ``SVOM``, ``IceCube``, ``GWAK``, ``SGNL``
 - ``Search``: the search activity which led to the detection 
     - values: ``AllSky``, ``LowMass``, ``HighMass``, ``GRB``, ``Supernova``, ``MDC``, ``LowMassSim``, ``AllSkyLong``, ``O2VirgoTest``, ``BBH``, ``IMBH``, ``SubGRB``, ``EarlyWarning``, ``SubGRBTargeted``, ``SSM``, ``FRB``, ``LensingSubthreshold``, ``VTInjection``, ``HEN``
 
diff --git a/gracedb/events/migrations/0102_add_sgnl_pipeline.py b/gracedb/events/migrations/0102_add_sgnl_pipeline.py
new file mode 100644
index 000000000..a532e68da
--- /dev/null
+++ b/gracedb/events/migrations/0102_add_sgnl_pipeline.py
@@ -0,0 +1,34 @@
+from django.db import migrations
+from events.models import Pipeline
+
+# Creates initial search pipeline instances
+
+# List of search pipeline names
+NEW_PIPELINES = [
+    ('SGNL', Pipeline.PIPELINE_TYPE_SEARCH_PRODUCTION)
+]
+
+def add_pipelines(apps, schema_editor):
+    Pipeline = apps.get_model('events', 'Pipeline')
+
+    # Create pipelines
+    for pipeline_name in NEW_PIPELINES:
+        pipeline, created = Pipeline.objects.get_or_create(name=pipeline_name[0])
+        pipeline.pipeline_type = pipeline_name[1]
+        pipeline.save()
+
+def remove_pipelines(apps, schema_editor):
+    Pipeline = apps.get_model('events', 'Pipeline')
+
+    # Delete pipelines
+    Pipeline.objects.filter(name__in=NEW_PIPELINES).delete()
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('events', '0101_remove_mlyburstevent_channel_mlyburstevent_channels'),
+    ]
+
+    operations = [
+        migrations.RunPython(add_pipelines, remove_pipelines),
+    ]
diff --git a/gracedb/events/translator.py b/gracedb/events/translator.py
index def85d646..2feba133d 100644
--- a/gracedb/events/translator.py
+++ b/gracedb/events/translator.py
@@ -117,7 +117,7 @@ def handle_uploaded_data(event, datafilename,
 
     pipeline = event.pipeline.name
 
-    if pipeline in [ 'gstlal', 'spiir', 'pycbc', 'PyGRB'] or (pipeline in ['MBTA', 'MBTAOnline'] and '.xml' in datafilename):
+    if pipeline in [ 'gstlal', 'spiir', 'pycbc', 'PyGRB', 'SGNL'] or (pipeline in ['MBTA', 'MBTAOnline'] and '.xml' in datafilename):
         log_comment = "Log File Created"
         # Wildly speculative wrt HM
 
diff --git a/gracedb/events/view_logic.py b/gracedb/events/view_logic.py
index 2d7e632df..b630291cb 100644
--- a/gracedb/events/view_logic.py
+++ b/gracedb/events/view_logic.py
@@ -55,10 +55,9 @@ def _createEventFromForm(request, form):
         else:
             search = None
         # Create Event
-        if pipeline.name in ['gstlal', 'spiir', 'MBTAOnline', 'MBTA', 'pycbc', 'PyGRB']:
+        if pipeline.name in settings.COINC_PIPELINES:
             event = CoincInspiralEvent()
-        elif pipeline.name in ['Fermi', 'Swift', 'SNEWS','INTEGRAL','AGILE', 'CHIME',
-                               'SVOM']:
+        elif pipeline.name in settings.GRB_PIPELINES:
             event = GrbEvent()
         elif pipeline.name in ['CWB', 'CWB2G']:
             event = MultiBurstEvent() 
-- 
GitLab


From f374b4cab193b5637afb9b84d9d0f48e69060de2 Mon Sep 17 00:00:00 2001
From: Alexander Pace <alexander.pace@ligo.org>
Date: Wed, 15 Jan 2025 12:51:32 -0600
Subject: [PATCH 3/5] add SGNL to pipelines not approved for production

---
 config/settings/container/production.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/config/settings/container/production.py b/config/settings/container/production.py
index b6c857625..84340199d 100644
--- a/config/settings/container/production.py
+++ b/config/settings/container/production.py
@@ -87,4 +87,4 @@ if (DEBUG == True):
     raise RuntimeError("Turn off debug mode for production")
 
 # Hardcode pipelines not approved for production:
-UNAPPROVED_PIPELINES += ['aframe', 'GWAK']
+UNAPPROVED_PIPELINES += ['aframe', 'GWAK', 'SGNL']
-- 
GitLab


From 1a1dbea4140ebcc35c2b02e3c57d98028f9c3be4 Mon Sep 17 00:00:00 2001
From: Alexander Pace <alexander.pace@ligo.org>
Date: Thu, 16 Jan 2025 12:04:06 -0600
Subject: [PATCH 4/5] add uploaders for sgnl

---
 .../guardian/0026_update_gstlal_robots.py     | 67 ++++++++++++++++
 .../guardian/0027_populate_sgnl_uploaders.py  | 78 +++++++++++++++++++
 2 files changed, 145 insertions(+)
 create mode 100644 gracedb/migrations/guardian/0026_update_gstlal_robots.py
 create mode 100644 gracedb/migrations/guardian/0027_populate_sgnl_uploaders.py

diff --git a/gracedb/migrations/guardian/0026_update_gstlal_robots.py b/gracedb/migrations/guardian/0026_update_gstlal_robots.py
new file mode 100644
index 000000000..a024e69ad
--- /dev/null
+++ b/gracedb/migrations/guardian/0026_update_gstlal_robots.py
@@ -0,0 +1,67 @@
+from django.db import migrations
+
+# Creates UserObjectPermission objects which allow specific users
+# to add events for pycbc. Also removes users that aren't in the 
+# collaboration anymore. They couldn't log in and up
+
+PIPELINE_NAME = 'gstlal'
+
+USERS_TO_ADD = [
+     'cort.posnansky@ligo.org',
+     'gstlalcbc_offline',
+      ]
+
+def add_permissions(apps, schema_editor):
+    User = apps.get_model('auth', 'User')
+    Permission = apps.get_model('auth', 'Permission')
+    UserObjectPermission = apps.get_model('guardian', 'UserObjectPermission')
+    Pipeline = apps.get_model('events', 'Pipeline')
+    ContentType = apps.get_model('contenttypes', 'ContentType')
+
+    perm = Permission.objects.get(codename='populate_pipeline')
+    ctype = ContentType.objects.get_for_model(Pipeline)
+
+    pipeline, created  = Pipeline.objects.get_or_create(name=PIPELINE_NAME)
+
+    # Now add the new people:
+    for u in USERS_TO_ADD:
+        # get the user object:
+        user, _  = User.objects.get_or_create(username=u)
+
+        # now get the corresponding userobjectpermission:
+        uop, _ = UserObjectPermission.objects.get_or_create(
+                user=user, permission=perm, content_type=ctype,
+                object_pk=pipeline.id)
+
+def remove_permissions(apps, schema_editor):
+    User = apps.get_model('auth', 'User')
+    Permission = apps.get_model('auth', 'Permission')
+    UserObjectPermission = apps.get_model('guardian', 'UserObjectPermission')
+    Pipeline = apps.get_model('events', 'Pipeline')
+    ContentType = apps.get_model('contenttypes', 'ContentType')
+
+    perm = Permission.objects.get(codename='populate_pipeline')
+    ctype = ContentType.objects.get_for_model(Pipeline)
+
+    pipeline, created  = Pipeline.objects.get_or_create(name=PIPELINE_NAME)
+    # first remove the new people:
+    for u in USERS_TO_ADD:
+        # get the user object:
+        user, _  = User.objects.get_or_create(username=u)
+
+        # now get the corresponding userobjectpermission:
+        uop, _ = UserObjectPermission.objects.get_or_create(
+                user=user, permission=perm, content_type=ctype,
+                object_pk=pipeline.id)
+
+        uop.delete()
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('guardian', '0025_populate_gwak_uploaders'),
+    ]
+
+    operations = [
+        migrations.RunPython(add_permissions, remove_permissions),
+    ]
diff --git a/gracedb/migrations/guardian/0027_populate_sgnl_uploaders.py b/gracedb/migrations/guardian/0027_populate_sgnl_uploaders.py
new file mode 100644
index 000000000..f5d5ac2c9
--- /dev/null
+++ b/gracedb/migrations/guardian/0027_populate_sgnl_uploaders.py
@@ -0,0 +1,78 @@
+from django.db import migrations
+
+# List of pipeline names and lists of usernames who should
+# be allowed to add events for them
+PP_LIST = [
+    {
+        'pipeline': 'SGNL',
+        'usernames': [
+            'sgnl-scitoken',
+            'yun-jing.huang@ligo.org',
+            'chad.hanna@ligo.org',
+            'emfollow',
+        ]
+    },
+]
+
+
+def add_permissions(apps, schema_editor):
+    User = apps.get_model('auth', 'User')
+    Permission = apps.get_model('auth', 'Permission')
+    UserObjectPermission = apps.get_model('guardian', 'UserObjectPermission')
+    Pipeline = apps.get_model('events', 'Pipeline')
+    ContentType = apps.get_model('contenttypes', 'ContentType')
+
+    perm = Permission.objects.get(codename='populate_pipeline')
+    ctype = ContentType.objects.get_for_model(Pipeline)
+
+    for pp_dict in PP_LIST:
+        pipeline, created = Pipeline.objects.get_or_create(name=pp_dict['pipeline'])
+
+        # Loop over users
+        for username in pp_dict['usernames']:
+
+            # get the user object
+            user, _ = User.objects.get_or_create(username=username)
+
+            # Create UserObjectPermission
+            uop, uop_created = UserObjectPermission.objects.get_or_create(
+                user=user, permission=perm, content_type=ctype,
+                object_pk=pipeline.id)
+
+
+def remove_permissions(apps, schema_editor):
+    User = apps.get_model('auth', 'User')
+    Permission = apps.get_model('auth', 'Permission')
+    UserObjectPermission = apps.get_model('guardian', 'UserObjectPermission')
+    Pipeline = apps.get_model('events', 'Pipeline')
+    ContentType = apps.get_model('contenttypes', 'ContentType')
+
+    perm = Permission.objects.get(codename='populate_pipeline')
+    ctype = ContentType.objects.get_for_model(Pipeline)
+
+    for pp_dict in PP_LIST:
+        pipeline, created = Pipeline.objects.get_or_create(name=pp_dict['pipeline'])
+
+        # Loop over users
+        for username in pp_dict['usernames']:
+
+            # get the user object
+            user, _ = User.objects.get_or_create(username=username)
+
+            # Remove UserObjectPermission
+            uop, _ = UserObjectPermission.objects.get_or_create(
+                user=user, permission=perm, content_type=ctype,
+                object_pk=pipeline.id)
+
+            uop.delete()
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('guardian', '0026_update_gstlal_robots'),
+        ('events', '0102_add_sgnl_pipeline'),
+    ]
+
+    operations = [
+        migrations.RunPython(add_permissions, remove_permissions),
+    ]
-- 
GitLab


From cce9d50d1a55808b64cef2c5f1a634b68917fb50 Mon Sep 17 00:00:00 2001
From: Alexander Pace <alexander.pace@ligo.org>
Date: Mon, 3 Feb 2025 14:06:15 -0600
Subject: [PATCH 5/5] add raven_report and cgmi blessed tags

---
 config/settings/base.py                       |  2 +
 .../migrations/0103_create_blessed_tags.py    | 37 +++++++++++++++++++
 gracedb/events/templatetags/mediaviews.py     |  4 +-
 3 files changed, 42 insertions(+), 1 deletion(-)
 create mode 100644 gracedb/events/migrations/0103_create_blessed_tags.py

diff --git a/config/settings/base.py b/config/settings/base.py
index 28fa0c49f..88eabc96d 100644
--- a/config/settings/base.py
+++ b/config/settings/base.py
@@ -245,6 +245,8 @@ BLESSED_TAGS = [
                  'pe',
                  'sig_info',
                  'audio',
+                 'raven_report',
+                 'cgmi',
                ]
 
 # Lists of pipelines used for selecting templates to serve
diff --git a/gracedb/events/migrations/0103_create_blessed_tags.py b/gracedb/events/migrations/0103_create_blessed_tags.py
new file mode 100644
index 000000000..d2cb895fb
--- /dev/null
+++ b/gracedb/events/migrations/0103_create_blessed_tags.py
@@ -0,0 +1,37 @@
+from django.db import migrations
+
+# Create a blessed tag for RAVEN:
+# https://git.ligo.org/computing/gracedb/server/-/issues/368
+# and for cgmi
+
+# List of tag names and display names
+BLESSED_TAGS = [
+    {'name': 'raven_report', 'displayName': 'RAVEN Report'},
+    {'name': 'cgmi', 'displayName': 'Coarse-Grained Mass Information'},
+]
+
+def add_tags(apps, schema_editor):
+    Tag = apps.get_model('events', 'Tag')
+
+    # Create tags
+    for tag_dict in BLESSED_TAGS:
+        tag, created = Tag.objects.get_or_create(name=tag_dict['name'])
+        if created:
+            tag.displayName = tag_dict['displayName']
+            tag.save()
+
+def remove_tags(apps, schema_editor):
+    Tag = apps.get_model('events', 'Tag')
+
+    # Delete tags
+    Tag.objects.filter(name__in=[s['name'] for s in BLESSED_TAGS]).delete()
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('events', '0102_add_sgnl_pipeline'),
+    ]
+
+    operations = [
+        migrations.RunPython(add_tags, remove_tags),
+    ]
diff --git a/gracedb/events/templatetags/mediaviews.py b/gracedb/events/templatetags/mediaviews.py
index 9b9382cbc..3414685e5 100644
--- a/gracedb/events/templatetags/mediaviews.py
+++ b/gracedb/events/templatetags/mediaviews.py
@@ -29,7 +29,9 @@ blessed_tag_priority_order = [
     'sig_info',
     'audio',
     'em_follow',
-    'pe'
+    'pe',
+    'raven_report',
+    'cgmi',
 ]
 
 
-- 
GitLab