Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
85 changes: 85 additions & 0 deletions tom_observations/facilities/lco_redirect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import logging
import urllib.parse

from crispy_forms.layout import HTML, Layout
from django.conf import settings
from django.shortcuts import get_object_or_404
from django.urls import reverse

from tom_observations.facility import GenericObservationFacility, GenericObservationForm
from tom_targets.models import Target

logger = logging.getLogger(__name__)


class LCORedirectForm(GenericObservationForm):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
target_id = self.initial.get('target_id')
target = get_object_or_404(Target, pk=target_id)
query_params = self.target_to_query_params(target)
request = self.initial.get('request', None)
if not request:
raise ValueError("LCORedirectForm requires request in initial data")
redirect_uri = request.build_absolute_uri(
reverse("tom_observations:callback")
) + f"?target_id={target.pk}&facility=LCO"
redirect_uri = urllib.parse.quote_plus(redirect_uri)
portal_uri = self.observation_portal_uri()
url = f"{portal_uri}/create?{query_params}&redirect_uri={redirect_uri}"
self.helper.layout = Layout(
HTML(f'''
<p>
This plugin will redirect you to the LCO global observation portal to
create an observation for this target.
You will be redirected back to the TOM once the observation is submitted.
</p>
<a class="btn btn-outline-primary" href="{url}">
Continue to lco.global
</a>
<a class="btn btn-outline-primary"
href="{{% url 'tom_targets:detail' {target_id} %}}?tab=observe">Back</a>
''')
)

def target_to_query_params(self, target) -> str:
set_fields = {"target_" + k: v for k, v in target.as_dict().items() if v is not None}
return urllib.parse.urlencode(set_fields)

def observation_portal_uri(self) -> str:
return settings.FACILITIES.get('LCO', {}).get('portal_url', 'https://observe.lco.global')


class LCORedirectFacility(GenericObservationFacility):
name = 'LCORedirect'
observation_forms = {
'ALL': LCORedirectForm,
}
observation_types = [('Default', '')]

def get_form(self, observation_type):
return LCORedirectForm

def get_template_form(self, observation_type):
pass

def submit_observation(self, observation_payload):
return

def validate_observation(self, observation_payload):
return

def get_observation_url(self, observation_id):
return

def get_terminal_observing_states(self):
return []

def get_observing_sites(self):
return {}

def get_observation_status(self, observation_id):
return

def data_products(self, observation_id, product_id=None):
return []
22 changes: 22 additions & 0 deletions tom_observations/tests/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,28 @@ def test_add_existing_observation_duplicate(self):
self.assertEqual(ObservationRecord.objects.filter(observation_id=obsr.observation_id).count(), 2)


@override_settings(TOM_FACILITY_CLASSES=['tom_observations.tests.utils.FakeRoboticFacility'],
TARGET_PERMISSIONS_ONLY=False)
class TestCallbackView(TestCase):
def setUp(self):
self.target = SiderealTargetFactory.create(permissions='PUBLIC')
self.user = User.objects.create_user(username='vincent_adultman', password='important')
self.client.force_login(self.user)

def test_callback(self):
"""
The callback url is constructed by the OCS and the user is redirected to it after
the observation record is created. This tests that a corresponding ObvservationRecord is created
on the TOM side, just as if one was created using the built-in OCS form.
The view should redirect the user to the detail view of the observation record.
"""
callback_params = f"?target_id={self.target.id}&facility=FakeRoboticFacility&observation_id=1234"
url = reverse('tom_observations:callback') + callback_params
response = self.client.get(url)
observation = ObservationRecord.objects.get(target=self.target, facility='FakeRoboticFacility')
self.assertRedirects(response, reverse('tom_observations:detail', kwargs={'pk': observation.pk}))


@override_settings(TOM_FACILITY_CLASSES=['tom_observations.tests.utils.FakeRoboticFacility'])
class TestFacilityStatusView(TestCase):
def setUp(self):
Expand Down
3 changes: 2 additions & 1 deletion tom_observations/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
ObservationGroupListView, ObservationListView, ObservationRecordCancelView,
ObservationRecordDetailView, ObservationTemplateCreateView,
ObservationTemplateDeleteView, ObservationTemplateListView,
ObservationTemplateUpdateView)
ObservationTemplateUpdateView, ObservationCallbackView)
from tom_observations.api_views import ObservationRecordViewSet
from tom_common.api_router import SharedAPIRootRouter

Expand All @@ -31,4 +31,5 @@
path('<int:pk>/cancel/', ObservationRecordCancelView.as_view(), name='cancel'),
path('<int:pk>/update/', ObservationRecordUpdateView.as_view(), name='update'),
path('<int:pk>/', ObservationRecordDetailView.as_view(), name='detail'),
path('callback/', ObservationCallbackView.as_view(), name='callback'),
]
29 changes: 28 additions & 1 deletion tom_observations/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
from django_filters import CharFilter, ChoiceFilter, DateTimeFromToRangeFilter, ModelMultipleChoiceFilter
from django_filters import OrderingFilter, MultipleChoiceFilter, rest_framework
from django_filters.views import FilterView
from django.shortcuts import redirect
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse, reverse_lazy
from django.utils.safestring import mark_safe
from django.views.generic import View, TemplateView
Expand Down Expand Up @@ -309,6 +309,7 @@ def get_initial(self):
raise Exception('Must provide target_id')
initial['target_id'] = self.get_target_id()
initial['facility'] = self.get_facility()
initial['request'] = self.request
initial.update(self.request.GET.dict())
return initial

Expand Down Expand Up @@ -421,6 +422,32 @@ def get(self, request, *args, **kwargs):
return redirect(reverse('tom_observations:detail', kwargs={'pk': obsr.id}))


class ObservationCallbackView(LoginRequiredMixin, View):
def get(self, request):
facility = request.GET.get('facility')
target_id = request.GET.get('target_id')
observation_id = request.GET.get('observation_id')
user = request.user
if not all([facility, target_id, observation_id]):
messages.error(self.request, 'Missing required parameters: facility, target_id, observation_id')
return redirect(reverse('tom_observations:list'))
target = get_object_or_404(Target, id=target_id)
observation, created = ObservationRecord.objects.get_or_create(
user=user,
facility=facility,
target=target,
observation_id=observation_id,
parameters=request.GET
)
assign_perm('tom_observations.view_observationrecord', user, observation)
assign_perm('tom_observations.change_observationrecord', user, observation)
assign_perm('tom_observations.delete_observationrecord', user, observation)
if not created:
messages.warning(self.request, "Observation record for this target and facility already exists.")

return redirect(reverse('tom_observations:detail', kwargs={'pk': observation.pk}))


class AddExistingObservationView(LoginRequiredMixin, FormView):
"""
View for associating a pre-existing observation with a target. Requires authentication.
Expand Down