diff --git a/CHANGELOG.md b/CHANGELOG.md index cb93af13..6251d851 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,14 @@ Types of changes: - `Fixed`: for any bug fixes. - `Security`: in case of vulnerabilities. +## [x.y.z] + +### Added + +- Added a new form to update properties of existing transients +- Form prompts retriggering of updated transients from the relevant stages +- Added a new migration to record comments on any user-updated properties + ## [1.12.0] ### Added diff --git a/app/host/forms.py b/app/host/forms.py index 9d10600b..2d372382 100644 --- a/app/host/forms.py +++ b/app/host/forms.py @@ -49,4 +49,14 @@ class TransientUploadForm(forms.Form): label='Transient definition table', required=False, ) + update_info = forms.CharField( + widget=forms.Textarea(attrs={ + 'style': 'min-width: 30rem;', + 'cols': 100, + 'placeholder': 'Identifier, RA, Dec, Redshift, Classification, HostName, HostRA, HostDec, HostRedshift, Global_aperture_a, Global_aperture_b, Global_aperture_theta, Comment' + }), + label='Transient update table', + required=False, + ) + file = forms.FileField(required=False) diff --git a/app/host/migrations/0051_transient_update_comment_transient_update_fields_and_more.py b/app/host/migrations/0051_transient_update_comment_transient_update_fields_and_more.py new file mode 100644 index 00000000..7ae87683 --- /dev/null +++ b/app/host/migrations/0051_transient_update_comment_transient_update_fields_and_more.py @@ -0,0 +1,23 @@ +# Generated by Django 5.1.14 on 2026-06-23 00:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('host', '0050_alter_host_name_alias'), + ] + + operations = [ + migrations.AddField( + model_name='transient', + name='update_comment', + field=models.CharField(blank=True, max_length=500, null=True), + ), + migrations.AddField( + model_name='transient', + name='update_fields', + field=models.CharField(blank=True, max_length=500, null=True), + ), + ] diff --git a/app/host/models.py b/app/host/models.py index 43599689..f0e800bc 100644 --- a/app/host/models.py +++ b/app/host/models.py @@ -162,7 +162,9 @@ def validate_name(name): added_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) progress = models.IntegerField(default=0) software_version = models.CharField(max_length=50, blank=True, null=True) - + update_comment = models.CharField(max_length=500, blank=True, null=True) + update_fields = models.CharField(max_length=500, blank=True, null=True) + @property def best_redshift(self): """get the best redshift for a transient""" diff --git a/app/host/templates/host/add_transient.html b/app/host/templates/host/add_transient.html index c07d72f1..69232e6c 100644 --- a/app/host/templates/host/add_transient.html +++ b/app/host/templates/host/add_transient.html @@ -57,7 +57,7 @@

Add Transients

- There are three ways to add transients to the Blast database and trigger their data processing. + There are four ways to add or update transients to the Blast database and trigger their data processing. Click a method below to enter information:

@@ -71,6 +71,10 @@

Add Transients

  Define transients by specifying properties. + + +   Update properties of existing transients. +   Import transient from an exported archive file. @@ -111,6 +115,28 @@

Add Transients

{% endif %} + {% if updated_transient_names|length %} +
+

The following transients were updated and re-triggered:

+
+
+ {% endif %} + + {% if not_existing_transient_names|length %} +
+

The following transients were not found in the database:

+ +
+ {% endif %} + {% if defined_transient_names|length %}

The following transients were successfully added to the Blast database:

@@ -170,6 +196,28 @@

Add Transients

{{ form.full_info | as_crispy_field }}
+
+

Update transients by supplying a comma-separated value (CSV) table, + where each row defines a transient with the following columns of information:
+ Identifier, RA, Dec, Redshift, Classification, HostName, HostRA, HostDec, HostRedshift, Global_aperture_a, Global_aperture_b, Global_aperture_theta, Comment +

+

A transient will not be added if the name already exists in the database or if the coordinates are within one arcsecond of an existing transient.

+

+
+ {{ form.update_info | as_crispy_field }} +
+
+
@@ -184,9 +232,11 @@

Add Transients

function showImportForm() { document.getElementById("import-form").style.display = "block"; document.getElementById("define-form").style.display = "none"; + document.getElementById("update-form").style.display = "none"; document.getElementById("import-archive-form").style.display = "none"; document.getElementById("import-button").classList.add("active"); document.getElementById("define-button").classList.remove("active"); + document.getElementById("update-button").classList.remove("active"); document.getElementById("upload-archive-button").classList.remove("active"); document.getElementById("submit-btn").style.display = "block"; document.getElementById("submit-btn").innerHTML = "Submit"; @@ -196,9 +246,11 @@

Add Transients

function showCreateForm() { document.getElementById("import-form").style.display = "none"; document.getElementById("define-form").style.display = "block"; + document.getElementById("update-form").style.display = "none"; document.getElementById("import-archive-form").style.display = "none"; document.getElementById("import-button").classList.remove("active"); document.getElementById("define-button").classList.add("active"); + document.getElementById("update-button").classList.remove("active"); document.getElementById("upload-archive-button").classList.remove("active"); document.getElementById("submit-btn").style.display = "block"; document.getElementById("submit-btn").innerHTML = "Submit"; @@ -208,9 +260,11 @@

Add Transients

function showImportArchiveForm() { document.getElementById("import-form").style.display = "none"; document.getElementById("define-form").style.display = "none"; + document.getElementById("update-form").style.display = "none"; document.getElementById("import-archive-form").style.display = "block"; document.getElementById("import-button").classList.remove("active"); document.getElementById("define-button").classList.remove("active"); + document.getElementById("update-button").classList.remove("active"); document.getElementById("upload-archive-button").classList.add("active"); document.getElementById("submit-btn").style.display = "block"; document.getElementById("submit-btn").innerHTML = "Upload"; @@ -218,5 +272,20 @@

Add Transients

document.getElementById("id_full_info").value = ""; document.getElementById("add-form").enctype = "multipart/form-data"; } + function showUpdateForm() { + document.getElementById("import-form").style.display = "none"; + document.getElementById("define-form").style.display = "none"; + document.getElementById("update-form").style.display = "block"; + document.getElementById("import-archive-form").style.display = "none"; + document.getElementById("import-button").classList.remove("active"); + document.getElementById("define-button").classList.remove("active"); + document.getElementById("update-button").classList.add("active"); + document.getElementById("upload-archive-button").classList.remove("active"); + document.getElementById("submit-btn").style.display = "block"; + document.getElementById("submit-btn").innerHTML = "Submit"; + document.getElementById("id_tns_names").value = ""; + document.getElementById("add-form").removeAttribute("enctype"); + } + {% endblock %} diff --git a/app/host/tests/test_views.py b/app/host/tests/test_views.py index f391076a..6beeb27e 100644 --- a/app/host/tests/test_views.py +++ b/app/host/tests/test_views.py @@ -1,7 +1,11 @@ from django.test import TestCase from textwrap import dedent from django.contrib.auth.models import User +from django.db.models import Q from ..host_utils import import_transient_info +from host.models import Transient +from host.models import Aperture +from host.models import TaskRegister class ViewTest(TestCase): @@ -19,6 +23,92 @@ def test_transient_page(self): self.assertEqual(response.status_code, 200) +class ModifyTransientTest(TestCase): + def setUp(self): + import base64 + username = 'testuser' + b64username = base64.urlsafe_b64encode(username.encode('utf-8')).decode('utf-8').strip('=') + self.credentials = {"username": b64username, "password": "secret"} + User.objects.create_user(**self.credentials, is_superuser=True) + self.client.login(**self.credentials) + with open('''/data/transient_datasets/2026dix.tar.gz''', 'rb') as dataset_fileobj: + import_transient_info(dataset_fileobj) + with open('''/data/transient_datasets/2026dgt.tar.gz''', 'rb') as dataset_fileobj: + import_transient_info(dataset_fileobj) + + def test_add_tansients_by_definition(self): + # TODO: This test is fragile due to the explicit HTML string search. + response = self.client.post("/add/", data={ + 'update_info': dedent(""" + 2026dix,None,None,0.05,SN Ia,None,None,None,None,None,None,None,z and class test + 2026dgt,None,None,None,None,None,132.3561625,29.5105694,None,5,5,0,host pos test + """)}) + self.assertEqual(response.status_code, 200) + # Check that transients were updated + self.assertContains( + response, + text=("""

The following transients were updated and re-triggered:

\n