Source code for jwql.website.apps.jwql.views

"""Defines the views for the ``jwql`` web app.

In Django, "a view function, or view for short, is simply a Python
function that takes a Web request and returns a Web response" (from
Django documentation). This module defines all of the views that are
used to generate the various webpages used for the JWQL application.
For example, these views can list the tools available to users, query
the ``jwql`` database, and display images and headers.

Authors
-------

    - Lauren Chambers
    - Johannes Sahlmann
    - Teagan King
    - Mees Fix
    - Bryan Hilbert
    - Maria Pena-Guerrero
    - Bradley Sappington
    - Melanie Clarke


Use
---

    This module is called in ``urls.py`` as such:
    ::

        from django.urls import path
        from . import views
        urlpatterns = [path('web/path/to/view/', views.view_name,
        name='view_name')]

References
----------
    For more information please see:
        ``https://docs.djangoproject.com/en/2.0/topics/http/views/``

Dependencies
------------
    The user must have a configuration file named ``config.json``
    placed in the ``jwql`` directory.
"""

import csv
import datetime
import json
import glob
import logging
import os
import operator
import socket

from astropy.time import Time
from bokeh.layouts import layout
from bokeh.embed import components
from django.core.paginator import Paginator
from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect, render
import numpy as np
from sqlalchemy import inspect

from jwql.database.database_interface import load_connection
from jwql.utils import monitor_utils
from jwql.utils.interactive_preview_image import InteractivePreviewImg
from jwql.utils.constants import JWST_INSTRUMENT_NAMES_MIXEDCASE, URL_DICT, QUERY_CONFIG_TEMPLATE, QueryConfigKeys
from jwql.utils.utils import filename_parser, filesystem_path, get_base_url, get_config
from jwql.utils.utils import get_rootnames_for_instrument_proposal, query_unformat

from .data_containers import build_table
from .data_containers import get_acknowledgements
from .data_containers import get_additional_exposure_info
from .data_containers import get_available_suffixes
from .data_containers import get_anomaly_form
from .data_containers import get_dashboard_components
from .data_containers import get_edb_components
from .data_containers import get_explorer_extension_names
from .data_containers import get_header_info
from .data_containers import get_image_info
from .data_containers import get_instrument_looks
from .data_containers import get_rootnames_from_query
from .data_containers import random_404_page
from .data_containers import text_scrape
from .data_containers import thumbnails_ajax
from .data_containers import thumbnails_query_ajax
from .forms import JwqlQueryForm
from .forms import FileSearchForm
if not os.environ.get("READTHEDOCS"):
    from .models import RootFileInfo
from astropy.io import fits


[docs] def jwql_query(request): """The anomaly query form page""" form = JwqlQueryForm(request.POST or None) form.fields['sort_type'].initial = request.session.get('image_sort', 'Recent') if request.method == 'POST': if form.is_valid(): query_configs = {} for instrument in ['miri', 'nirspec', 'niriss', 'nircam', 'fgs']: query_configs[instrument] = {} query_configs[instrument]['filters'] = [query_unformat(i) for i in form.cleaned_data['{}_filt'.format(instrument)]] query_configs[instrument]['apertures'] = [query_unformat(i) for i in form.cleaned_data['{}_aper'.format(instrument)]] query_configs[instrument]['detectors'] = [query_unformat(i) for i in form.cleaned_data['{}_detector'.format(instrument)]] query_configs[instrument]['exptypes'] = [query_unformat(i) for i in form.cleaned_data['{}_exptype'.format(instrument)]] query_configs[instrument]['readpatts'] = [query_unformat(i) for i in form.cleaned_data['{}_readpatt'.format(instrument)]] query_configs[instrument]['gratings'] = [query_unformat(i) for i in form.cleaned_data['{}_grating'.format(instrument)]] query_configs[instrument]['subarrays'] = [query_unformat(i) for i in form.cleaned_data['{}_subarray'.format(instrument)]] query_configs[instrument]['pupils'] = [query_unformat(i) for i in form.cleaned_data['{}_pupil'.format(instrument)]] query_configs[instrument]['anomalies'] = [query_unformat(i) for i in form.cleaned_data['{}_anomalies'.format(instrument)]] all_filters, all_apers, all_detectors, all_exptypes = {}, {}, {}, {} all_readpatts, all_gratings, all_subarrays, all_pupils, all_anomalies = {}, {}, {}, {}, {} for instrument in query_configs: all_filters[instrument] = query_configs[instrument]['filters'] all_apers[instrument] = query_configs[instrument]['apertures'] all_detectors[instrument] = query_configs[instrument]['detectors'] all_exptypes[instrument] = query_configs[instrument]['exptypes'] all_readpatts[instrument] = query_configs[instrument]['readpatts'] all_gratings[instrument] = query_configs[instrument]['gratings'] all_subarrays[instrument] = query_configs[instrument]['subarrays'] all_pupils[instrument] = query_configs[instrument]['pupils'] all_anomalies[instrument] = query_configs[instrument]['anomalies'] parameters = QUERY_CONFIG_TEMPLATE.copy() parameters[QueryConfigKeys.INSTRUMENTS] = form.cleaned_data['instrument'] parameters[QueryConfigKeys.LOOK_STATUS] = form.cleaned_data['look_status'] parameters[QueryConfigKeys.DATE_RANGE] = form.cleaned_data['date_range'] parameters[QueryConfigKeys.PROPOSAL_CATEGORY] = form.cleaned_data['proposal_category'] parameters[QueryConfigKeys.SORT_TYPE] = form.cleaned_data['sort_type'] parameters[QueryConfigKeys.ANOMALIES] = all_anomalies parameters[QueryConfigKeys.APERTURES] = all_apers parameters[QueryConfigKeys.FILTERS] = all_filters parameters[QueryConfigKeys.DETECTORS] = all_detectors parameters[QueryConfigKeys.EXP_TYPES] = all_exptypes parameters[QueryConfigKeys.READ_PATTS] = all_readpatts parameters[QueryConfigKeys.GRATINGS] = all_gratings parameters[QueryConfigKeys.SUBARRAYS] = all_subarrays parameters[QueryConfigKeys.PUPILS] = all_pupils # save the query config settings to a session request.session['query_config'] = parameters return redirect('/query_submit') context = {'form': form, 'inst': ''} template = 'jwql_query.html' return render(request, template, context)
[docs] def about(request): """Generate the ``about`` page Parameters ---------- request : HttpRequest object Incoming request from the webpage Returns ------- HttpResponse object Outgoing response sent to the webpage """ template = 'about.html' acknowledgements = get_acknowledgements() context = {'acknowledgements': acknowledgements, 'inst': ''} return render(request, template, context)
[docs] def api_landing(request): """Generate the ``api`` page Parameters ---------- request : HttpRequest object Incoming request from the webpage Returns ------- HttpResponse object Outgoing response sent to the webpage """ template = 'api_landing.html' context = {'inst': ''} return render(request, template, context)
[docs] def save_page_navigation_data_ajax(request): """ Takes a bracketless string of rootnames and expstarts, and saves it as a session dictionary Parameters ---------- request: HttpRequest object Incoming request from the webpage Returns ------- HttpResponse object Outgoing response sent to the webpage """ # a string of the form " 'rootname1'='expstart1', 'rootname2'='expstart2', ..." if request.method == 'POST': navigate_dict = request.POST.get('navigate_dict') # Save session in form {rootname:expstart} rootname_expstarts = dict() for item in navigate_dict.split(','): rootname, expstart = item.split("=") rootname_expstarts[rootname] = float(expstart) request.session['navigation_data'] = rootname_expstarts context = {'item': request.session['navigation_data']} return JsonResponse(context, json_dumps_params={'indent': 2})
[docs] def archived_proposals(request, inst): """Generate the page listing all archived proposals in the database Parameters ---------- request : HttpRequest object Incoming request from the webpage inst : str Name of JWST instrument Returns ------- HttpResponse object Outgoing response sent to the webpage """ # Ensure the instrument is correctly capitalized inst = JWST_INSTRUMENT_NAMES_MIXEDCASE[inst.lower()] template = 'archive.html' context = {'inst': inst, 'base_url': get_base_url()} return render(request, template, context)
[docs] def archived_proposals_ajax(request, inst): """Generate the page listing all archived proposals in the database Parameters ---------- request : HttpRequest object Incoming request from the webpage inst : str Name of JWST instrument Returns ------- JsonResponse object Outgoing response sent to the webpage """ # Read in the json file created by data_containers.create_archived_proposals_context # and use as the context output_dir = get_config()['outputs'] context_file = os.path.join(output_dir, 'archive_page', f'{inst}_archive_context.json') with open(context_file, 'r') as obj: context = json.load(obj) return JsonResponse(context, json_dumps_params={'indent': 2})
[docs] def archive_thumbnails_ajax(request, inst, proposal, observation=None): """Generate the page listing archived images by proposal. Parameters ---------- request : HttpRequest object Incoming request from the webpage inst : str Name of JWST instrument proposal : str Number of observing proposal observation : str Observation number within the proposal Returns ------- JsonResponse object Outgoing response sent to the webpage """ # Ensure the instrument is correctly capitalized inst = JWST_INSTRUMENT_NAMES_MIXEDCASE[inst.lower()] data = thumbnails_ajax(inst, proposal, obs_num=observation) data['thumbnail_sort'] = request.session.get("image_sort", "Recent") data['thumbnail_group'] = request.session.get("image_group", "Exposure") save_page_navigation_data(request, data) return JsonResponse(data, json_dumps_params={'indent': 2})
[docs] def archive_thumbnails_per_observation(request, inst, proposal, observation): """Generate the page listing all archived images in the database for a certain proposal Parameters ---------- request : HttpRequest object Incoming request from the webpage inst : str Name of JWST instrument proposal : str Number of observing proposal observation : str Observation number within the proposal Returns ------- HttpResponse object Outgoing response sent to the webpage """ # Ensure the instrument is correctly capitalized inst = JWST_INSTRUMENT_NAMES_MIXEDCASE[inst.lower()] proposal_meta = text_scrape(proposal) # Get a list of all observation numbers for the proposal # This will be used to create buttons for observation-specific # pages rootnames = get_rootnames_for_instrument_proposal(inst, proposal) all_obs = [] for root in rootnames: try: all_obs.append(filename_parser(root)['observation']) except KeyError: pass obs_list = sorted(list(set(all_obs))) sort_type = request.session.get('image_sort', 'Recent') group_type = request.session.get('image_group', 'Exposure') template = 'thumbnails_per_obs.html' context = {'base_url': get_base_url(), 'inst': inst, 'obs': observation, 'obs_list': obs_list, 'prop': proposal, 'prop_meta': proposal_meta, 'sort': sort_type, 'group': group_type} return render(request, template, context)
[docs] def archive_thumbnails_query_ajax(request): """Generate the page listing archived images by query parameters. Parameters ---------- request : HttpRequest object Incoming request from the webpage inst : str Name of JWST instrument proposal : str Number of observing proposal Returns ------- JsonResponse object Outgoing response sent to the webpage """ parameters = request.session.get("query_config", QUERY_CONFIG_TEMPLATE.copy()) filtered_rootnames = get_rootnames_from_query(parameters) paginator = Paginator(filtered_rootnames, parameters[QueryConfigKeys.NUM_PER_PAGE]) page_number = request.GET.get("page", 1) page_obj = paginator.get_page(page_number) data = thumbnails_query_ajax(page_obj.object_list) data['thumbnail_sort'] = parameters[QueryConfigKeys.SORT_TYPE] data['thumbnail_group'] = request.session.get("image_group", "Exposure") # add top level parameters for summarizing data['query_config'] = {} for key in parameters: value = parameters[key] if isinstance(value, dict): for subkey in value: subvalue = value[subkey] if subvalue: data['query_config'][f'{key}_{subkey}'] = subvalue elif value: data['query_config'][key] = value # pass pagination info if page_obj.has_previous(): data['previous_page'] = page_obj.previous_page_number() data['current_page'] = page_obj.number if page_obj.has_next(): data['next_page'] = page_obj.next_page_number() data['total_pages'] = paginator.num_pages data['total_files'] = paginator.count request.session['image_sort'] = parameters[QueryConfigKeys.SORT_TYPE] save_page_navigation_data(request, data) return JsonResponse(data, json_dumps_params={'indent': 2})
[docs] def dashboard(request): """Generate the dashboard page Parameters ---------- request : HttpRequest object Incoming request from the webpage Returns ------- HttpResponse object Outgoing response sent to the webpage """ template = 'dashboard.html' db = get_dashboard_components(request) pie_graph = db.dashboard_instrument_pie_chart() files_graph = db.dashboard_files_per_day() useage_graph = db.dashboard_disk_usage() directories_usage_graph, central_store_usage_graph = db.dashboard_central_store_data_volume() filetype_bar = db.dashboard_filetype_bar_chart() table_columns, table_values = db.dashboard_monitor_tracking() grating_plot = db.dashboard_exposure_count_by_filter() anomaly_plot = db.dashboard_anomaly_per_instrument() plot = layout([[files_graph, useage_graph], [directories_usage_graph, central_store_usage_graph], [pie_graph, filetype_bar], [grating_plot, anomaly_plot]], sizing_mode='stretch_width') script, div = components(plot) time_deltas = ['All Time', '1 Day', '1 Week', '1 Month', '1 Year'] context = {'inst': '', 'script': script, 'div': div, 'table_columns': table_columns, 'table_rows': table_values, 'time_deltas': time_deltas} return render(request, template, context)
[docs] def download_report(request, inst): """Download data report by look status. Parameters ---------- request : HttpRequest object Incoming request from the webpage. inst : str The JWST instrument of interest. Returns ------- response : HttpResponse object Outgoing response sent to the webpage """ # check for filter criteria passed in request kwargs = dict() for filter_name in ['look', 'exp_type', 'cat_type', 'proposal', 'sort_as']: kwargs[filter_name] = request.GET.get(filter_name) # get all observation looks from file info model # and join with observation descriptors keys, looks = get_instrument_looks(inst, **kwargs) today = datetime.datetime.now().strftime('%Y%m%d') filename = f'{inst.lower()}_report_{today}.csv' response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = f'attachment; filename="{filename}"' writer = csv.writer(response) writer.writerow(keys) for row in looks: writer.writerow(row.values()) return response
[docs] def engineering_database(request): """Generate the EDB page. Parameters ---------- request : HttpRequest object Incoming request from the webpage Returns ------- HttpResponse object Outgoing response sent to the webpage """ edb_components = get_edb_components(request) template = 'engineering_database.html' context = {'inst': '', 'edb_components': edb_components} return render(request, template, context)
[docs] def export(request, tablename): """Function to export and download data from JWQLDB Table Viewer Parameters ---------- request : HttpRequest object Incoming request from the webpage tablename : str Name of table to download Returns ------- response : HttpResponse object Outgoing response sent to the webpage """ table_meta = build_table(tablename) response = HttpResponse(content_type='text/csv') response['Content-Disposition'] = 'attachment; filename="{}.csv"'.format(tablename) writer = csv.writer(response) writer.writerow(table_meta.columns.values) for _, row in table_meta.iterrows(): writer.writerow(row.values) return response
[docs] def home(request): """Generate the home page Parameters ---------- request : HttpRequest object Incoming request from the webpage Returns ------- HttpResponse object Outgoing response sent to the webpage """ # Create a form instance and populate it with data from the request form = FileSearchForm(request.POST or None) # If this is a POST request, we need to process the form data if request.method == 'POST': if form.is_valid(): return form.redirect_to_files() template = 'home.html' context = {'inst': '', 'form': form} return render(request, template, context)
[docs] def instrument(request, inst): """Generate the instrument tool index page. Parameters ---------- request : HttpRequest object Incoming request from the webpage inst : str Name of JWST instrument Returns ------- HttpResponse object Outgoing response sent to the webpage """ # Ensure the instrument is correctly capitalized inst = JWST_INSTRUMENT_NAMES_MIXEDCASE[inst.lower()] template = 'instrument.html' doc_url = URL_DICT[inst.lower()] context = {'inst': inst, 'doc_url': doc_url} return render(request, template, context)
[docs] def jwqldb_table_viewer(request, tablename_param=None): """Generate the JWQL Table Viewer view. Parameters ---------- request : HttpRequest object Incoming request from the webpage tablename_param : str Table name parameter from URL Returns ------- HttpResponse object Outgoing response sent to the webpage """ try: tablename = request.POST['db_table_select'] except KeyError: if tablename_param: tablename = tablename_param else: tablename = None if tablename is None: table_meta = None else: table_meta = build_table(tablename) _, _, engine, _ = load_connection(get_config()['connection_string']) all_jwql_tables = inspect(engine).get_table_names() if 'django_migrations' in all_jwql_tables: all_jwql_tables.remove('django_migrations') # No necessary information. jwql_tables_by_instrument = {} instruments = ['nircam', 'nirspec', 'niriss', 'miri', 'fgs'] # Sort tables by instrument for instrument in instruments: jwql_tables_by_instrument[instrument] = [tablename for tablename in all_jwql_tables if instrument in tablename] # Don't forget tables that dont contain instrument specific instrument information. jwql_tables_by_instrument['general'] = [table for table in all_jwql_tables if not any(instrument in table for instrument in instruments)] template = 'jwqldb_table_viewer.html' # If value of table_meta is None (when coming from home page) if table_meta is None: context = { 'inst': '', 'all_jwql_tables': jwql_tables_by_instrument} # If table_meta is empty, just render table with no data. elif table_meta.empty: context = { 'inst': '', 'all_jwql_tables': jwql_tables_by_instrument, 'table_columns': table_meta.columns.values, 'table_name': tablename} # Else, everything is good to go, render the table. else: context = { 'inst': '', 'all_jwql_tables': jwql_tables_by_instrument, 'table_columns': table_meta.columns.values, 'table_rows': table_meta.values, 'table_name': tablename} return render(request, template, context)
[docs] def log_view(request): """Access JWQL monitoring logs from the web app. Parameters ---------- request : HttpRequest object Incoming request from the webpage Returns ------- HttpResponse object Outgoing response sent to the webpage """ template = 'log_view.html' log_path = get_config()['log_dir'] log_name = request.POST.get('log_submit', None) hostname = socket.gethostname() if 'dljwql' in hostname: server = 'dev' elif 'tljwql' in hostname: server = 'test' else: server = 'ops' full_log_paths = sorted(glob.glob(os.path.join(log_path, server, '*', '*')), reverse=True) full_log_paths = [log for log in full_log_paths if not os.path.basename(log).startswith('.')] log_dictionary = {os.path.basename(path): path for path in full_log_paths} if log_name: with open(log_dictionary[log_name]) as f: log_text = f.read() else: log_text = None context = {'inst': '', 'all_logs': log_dictionary, 'log_text': log_text, 'log_name': log_name} return render(request, template, context)
[docs] def not_found(request, *kwargs): """Generate a ``not_found`` page Parameters ---------- request : HttpRequest object Incoming request from the webpage Returns ------- HttpResponse object Outgoing response sent to the webpage """ template = random_404_page() status_code = 404 # Note that this will show 400, 403, 404, and 500 as 404 status context = {'inst': ''} return render(request, template, context, status=status_code)
[docs] def query_submit(request): """Generate the page listing all archived images in the database for a certain proposal Parameters ---------- request : HttpRequest object Incoming request from the webpage Returns ------- HttpResponse object Outgoing response sent to the webpage """ template = 'query_submit.html' sort_type = request.session.get('image_sort', 'Recent') group_type = request.session.get('image_group', 'Exposure') page_number = request.GET.get("page", 1) context = {'inst': '', 'base_url': get_base_url(), 'sort': sort_type, 'group': group_type, 'page': page_number} return render(request, template, context)
[docs] def unlooked_images(request, inst): """Generate the page listing all unlooked images in the database Parameters ---------- request : HttpRequest object Incoming request from the webpage inst : str Name of JWST instrument Returns ------- HttpResponse object Outgoing response sent to the webpage """ pass
[docs] def view_header(request, inst, filename, filetype): """Generate the header view page Parameters ---------- request : HttpRequest object Incoming request from the webpage inst : str Name of JWST instrument filename : str FITS filename of selected image in filesystem filetype : str Type of file (e.g. ``uncal``) Returns ------- HttpResponse object Outgoing response sent to the webpage """ # Ensure the instrument is correctly capitalized inst = JWST_INSTRUMENT_NAMES_MIXEDCASE[inst.lower()] template = 'view_header.html' file_root = '_'.join(filename.split('_')) context = {'inst': inst, 'filename': filename, 'file_root': file_root, 'file_type': filetype, 'header_info': get_header_info(filename, filetype)} return render(request, template, context)
[docs] def explore_image(request, inst, file_root, filetype): """Generate the explore image page. Parameters ---------- request : HttpRequest object Incoming request from the webpage inst : str Name of JWST instrument file_root : str FITS file_root of selected image in filesystem filetype : str Type of file (e.g. ``uncal``) Returns ------- HttpResponse object Outgoing response sent to the webpage """ # Ensure the instrument is correctly capitalized inst = JWST_INSTRUMENT_NAMES_MIXEDCASE[inst.lower()] template = 'explore_image.html' # get explorable extensions from header extensions = get_explorer_extension_names(file_root, filetype) fits_file = file_root + '_' + filetype + '.fits' # Get image info containing all paths to fits files image_info_list = get_image_info(file_root) # Find index of our fits file fits_index = next(ix for ix, fits_path in enumerate(image_info_list['all_files']) if fits_file in fits_path) # get full path of fits file to open and extract extension info full_fits_file = image_info_list['all_files'][fits_index] extension_ints = {} extension_groups = {} # gather extension group/integration information to send if os.path.isfile(full_fits_file): with fits.open(full_fits_file) as hdulist: for exten in extensions: dims = hdulist[exten].shape if len(dims) == 4: extension_ints[exten], extension_groups[exten], ny, nx = dims elif len(dims) == 3: extension_groups[exten] = 0 extension_ints[exten], ny, nx = dims else: extension_ints[exten] = 0 extension_groups[exten] = 0 else: raise FileNotFoundError(f'WARNING: {full_fits_file} does not exist!') form = get_anomaly_form(request, inst, file_root) context = {'inst': inst, 'file_root': file_root, 'filetype': filetype, 'extensions': extensions, 'extension_groups': extension_groups, 'extension_ints': extension_ints, 'base_url': get_base_url(), 'form': form} return render(request, template, context)
[docs] def explore_image_ajax(request, inst, file_root, filetype, line_plots='false', low_lim=None, high_lim=None, ext_name="SCI", int1_nr=None, grp1_nr=None, int2_nr=None, grp2_nr=None): """Generate the page listing all archived images in the database for a certain proposal Parameters ---------- request : HttpRequest object Incoming request from the webpage inst : str Name of JWST instrument file_root : str FITS file_root of selected image in filesystem filetype : str Type of file (e.g. ``uncal``) line_plots : str If 'true', column and row plots will be computed and shown with the image. low_lim : str Signal value to use as the lower limit of the displayed image. If "None", it will be calculated using the ZScale function high_lim : str Signal value to use as the upper limit of the displayed image. If "None", it will be calculated using the ZScale function ext_name : str Extension to implement in interactive preview image ("SCI", "DQ", "GROUPDQ", "PIXELDQ", "ERR"...) Returns ------- JsonResponse object Outgoing response sent to the webpage """ # Ensure the instrument is correctly capitalized inst = JWST_INSTRUMENT_NAMES_MIXEDCASE[inst.lower()] # Get image info containing all paths to fits files image_info_list = get_image_info(file_root) # Save fits file name to use for bokeh image fits_file = file_root + '_' + filetype + '.fits' # Find index of our fits file fits_index = next(ix for ix, fits_path in enumerate(image_info_list['all_files']) if fits_file in fits_path) # get full path of fits file to send to InteractivePreviewImg full_fits_file = image_info_list['all_files'][fits_index] # sent floats not strings to init if low_lim == "None": low_lim = None if high_lim == "None": high_lim = None if int1_nr == "None": int1_nr = None if grp1_nr == "None": grp1_nr = None if int2_nr == "None": int2_nr = None if grp2_nr == "None": grp2_nr = None if low_lim is not None: low_lim = float(low_lim) if high_lim is not None: high_lim = float(high_lim) group = None integ = None if (grp1_nr): if (grp2_nr): group = [int(grp1_nr), int(grp2_nr)] else: group = int(grp1_nr) if (int1_nr): if (int2_nr): integ = [int(int1_nr), int(int2_nr)] else: integ = int(int1_nr) if str(line_plots).strip().lower() == 'true': line_plots = True else: line_plots = False int_preview_image = InteractivePreviewImg( full_fits_file, low_lim=low_lim, high_lim=high_lim, extname=ext_name, group=group, integ=integ, line_plots=line_plots) context = {'inst': "inst", 'script': int_preview_image.script, 'div': int_preview_image.div} return JsonResponse(context, json_dumps_params={'indent': 2})
[docs] def save_image_group_ajax(request): """Save the latest selected group type in the session. Parameters ---------- request : HttpRequest The incoming request. Returns ------- JsonResponse Object containing the group value as set in the session (key: 'item'). """ image_group = request.GET['group_type'] request.session['image_group'] = image_group context = {'item': request.session['image_group']} return JsonResponse(context, json_dumps_params={'indent': 2})
[docs] def save_image_sort_ajax(request): """Save the latest selected sort type in the session. Parameters ---------- request : HttpRequest The incoming request. Returns ------- JsonResponse Object containing the sort value as set in the session (key: 'item'). """ # a string of the form " 'rootname1'='expstart1', 'rootname2'='expstart2', ..." image_sort = request.GET['sort_type'] request.session['image_sort'] = image_sort context = {'item': request.session['image_sort']} return JsonResponse(context, json_dumps_params={'indent': 2})
[docs] def save_page_navigation_data(request, data): """ Save the data from the current page in the session. Enables navigating to the next or previous page. Current sort options are Ascending/Descending, and Recent/Oldest. Parameters ---------- request: HttpRequest object data: dictionary the data dictionary to be returned from the calling view function nav_by_date_range: boolean when viewing an image, will the next/previous buttons be sorted by date? (the other option is rootname) """ navigate_data = {} for rootname in data['file_data']: navigate_data[rootname] = data['file_data'][rootname]['expstart'] request.session['navigation_data'] = navigate_data return
[docs] def set_viewed_ajax(request, group_root, status): """Update the model's "viewed" field for a group of files Parameters ---------- request : HttpRequest object Incoming request from the webpage group_root : str Group root name, matching filename roots up to but not including the detector. status : {'new', 'viewed'} Value to set: 'new' for viewed=False, 'viewed' for viewed=True. Returns ------- JsonResponse object Outgoing response sent to the webpage """ viewed = (str(status).strip().lower() == 'viewed') root_file_info = RootFileInfo.objects.filter( root_name__startswith=group_root) for root_file in root_file_info: root_file.viewed = viewed root_file.save() # check actual status as set marked_viewed = all([rf.viewed for rf in root_file_info]) # Build the context context = {'marked_viewed': marked_viewed} return JsonResponse(context, json_dumps_params={'indent': 2})
[docs] def toggle_viewed_ajax(request, file_root): """Update the model's "mark_viewed" field and save in the database Parameters ---------- request : HttpRequest object Incoming request from the webpage file_root : str FITS file_root of selected image in filesystem Returns ------- JsonResponse object Outgoing response sent to the webpage """ root_file_info = RootFileInfo.objects.get(root_name=file_root) root_file_info.viewed = not root_file_info.viewed root_file_info.save() # Build the context context = {'marked_viewed': root_file_info.viewed} return JsonResponse(context, json_dumps_params={'indent': 2})
[docs] def view_exposure(request, inst, group_root): """Generate the exposure view page. Parameters ---------- request : HttpRequest object Incoming request from the webpage. inst : str Name of JWST instrument. group_root : str Exposure group, matching file root names up to but not including the detector. Returns ------- HttpResponse object Outgoing response sent to the webpage """ # Ensure the instrument is correctly capitalized inst = JWST_INSTRUMENT_NAMES_MIXEDCASE[inst.lower()] template = 'view_exposure.html' image_info = get_image_info(group_root) # Get the proposal id and obsnum from the group root name prop_id = group_root[2:7] obsnum = group_root[7:10] # Get available suffixes in a consistent order. suffixes = get_available_suffixes(image_info['suffixes'], return_untracked=False) # Get the anomaly submission form form = get_anomaly_form(request, inst, group_root) # if we get to this page without any navigation data, # previous/next buttons will be hidden navigation_data = request.session.get('navigation_data', {}) # For time based sorting options, sort to "Recent" first to create sorting consistency when times are the same. # This is consistent with how Tinysort is utilized in jwql.js->sort_by_thumbnails sort_type = request.session.get('image_sort', 'Recent') if sort_type in ['Descending']: matching_rootfiles = sorted(navigation_data, reverse=True) elif sort_type in ['Recent']: navigation_data = dict(sorted(navigation_data.items())) navigation_data = dict(sorted(navigation_data.items(), key=operator.itemgetter(1), reverse=True)) matching_rootfiles = list(navigation_data.keys()) elif sort_type in ['Oldest']: navigation_data = dict(sorted(navigation_data.items())) navigation_data = dict(sorted(navigation_data.items(), key=operator.itemgetter(1))) matching_rootfiles = list(navigation_data.keys()) else: matching_rootfiles = sorted(navigation_data) # pick out group names from matching root files group_root_list = [] for rootname in matching_rootfiles: try: other_group_root = filename_parser(rootname)['group_root'] except ValueError: continue if other_group_root not in group_root_list: group_root_list.append(other_group_root) # Get our current views RootFileInfo model and send our "viewed/new" information root_file_info = RootFileInfo.objects.filter(root_name__startswith=group_root) viewed = all([rf.viewed for rf in root_file_info]) # Convert expstart from MJD to a date expstart_str = Time(root_file_info[0].expstart, format='mjd').to_datetime().strftime('%d %b %Y %H:%M') # Create one dict of info to show at the top of the page, and another dict of info # to show in the collapsible text box. basic_info, additional_info = get_additional_exposure_info(root_file_info, image_info) # Build the context context = {'base_url': get_base_url(), 'group_root_list': group_root_list, 'inst': inst, 'prop_id': prop_id, 'obsnum': obsnum, 'group_root': group_root, 'suffixes': suffixes, 'num_ints': image_info['num_ints'], 'available_ints': image_info['available_ints'], 'total_ints': image_info['total_ints'], 'detectors': sorted(image_info['detectors']), 'form': form, 'marked_viewed': viewed, 'expstart_str': expstart_str, 'basic_info': basic_info, 'additional_info': additional_info} return render(request, template, context)
[docs] def view_image(request, inst, file_root): """Generate the image view page Parameters ---------- request : HttpRequest object Incoming request from the webpage inst : str Name of JWST instrument file_root : str FITS filename of selected image in filesystem Returns ------- HttpResponse object Outgoing response sent to the webpage """ # Ensure the instrument is correctly capitalized inst = JWST_INSTRUMENT_NAMES_MIXEDCASE[inst.lower()] template = 'view_image.html' image_info = get_image_info(file_root) # Put suffixes in a consistent order. Check if any of the # suffixes are not in the list that specifies order. suffixes, untracked_suffixes = get_available_suffixes( image_info['suffixes'], return_untracked=True) if len(untracked_suffixes) > 0: module = os.path.basename(__file__).strip('.py') monitor_utils.initialize_instrument_monitor(module) logging.warning((f'In view_image(), for {inst}, {file_root}, ' f'the following suffixes are present in the data, ' f'but not in EXPOSURE_PAGE_SUFFIX_ORDER in ' f'constants.py: {untracked_suffixes} ' 'Please add them, so that they will appear in a ' 'consistent order on the webpage.')) form = get_anomaly_form(request, inst, file_root) prop_id = file_root[2:7] # if we get to this page without any navigation data (i.e. direct link), # just use the file_root with no expstart time # navigate_data is dict of format rootname:expstart navigation_data = request.session.get('navigation_data', {file_root: 0}) # For time based sorting options, sort to "Recent" first to create # sorting consistency when times are the same. # This is consistent with how Tinysort is utilized in # jwql.js->sort_by_thumbnails sort_type = request.session.get('image_sort', 'Recent') if sort_type in ['Descending']: file_root_list = sorted(navigation_data, reverse=True) elif sort_type in ['Recent']: navigation_data = dict(sorted(navigation_data.items())) navigation_data = dict(sorted(navigation_data.items(), key=operator.itemgetter(1), reverse=True)) file_root_list = list(navigation_data.keys()) elif sort_type in ['Oldest']: navigation_data = dict(sorted(navigation_data.items())) navigation_data = dict(sorted(navigation_data.items(), key=operator.itemgetter(1))) file_root_list = list(navigation_data.keys()) else: file_root_list = sorted(navigation_data) # Get our current views RootFileInfo model and send our "viewed/new" information root_file_info = RootFileInfo.objects.get(root_name=file_root) # Convert expstart from MJD to a date expstart_str = Time(root_file_info.expstart, format='mjd').to_datetime().strftime('%d %b %Y %H:%M') # Create one dict of info to show at the top of the page, and another dict of info # to show in the collapsible text box. basic_info, additional_info = get_additional_exposure_info(root_file_info, image_info) # Build the context context = {'base_url': get_base_url(), 'file_root_list': file_root_list, 'inst': inst, 'prop_id': prop_id, 'obsnum': file_root[7:10], 'file_root': file_root, 'suffixes': suffixes, 'num_ints': image_info['num_ints'], 'available_ints': image_info['available_ints'], 'total_ints': image_info['total_ints'], 'form': form, 'marked_viewed': root_file_info.viewed, 'expstart_str': expstart_str, 'basic_info': basic_info, 'additional_info': additional_info} return render(request, template, context)