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

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 os

from bokeh.layouts import layout
from bokeh.embed import components
from django.http import HttpResponse, JsonResponse
from django.shortcuts import redirect, render

from jwql.database.database_interface import load_connection
from jwql.utils import anomaly_query_config
from jwql.utils.constants import JWST_INSTRUMENT_NAMES_MIXEDCASE, MONITORS, URL_DICT
from jwql.utils.utils import filesystem_path, get_base_url, get_config, query_unformat

from .data_containers import build_table
from .data_containers import data_trending
from .data_containers import get_acknowledgements
from .data_containers import get_current_flagged_anomalies
from .data_containers import get_dashboard_components
from .data_containers import get_edb_components
from .data_containers import get_filenames_by_instrument
from .data_containers import get_header_info
from .data_containers import get_image_info
from .data_containers import get_proposal_info
from .data_containers import get_thumbnails_all_instruments
from .data_containers import nirspec_trending
from .data_containers import random_404_page
from .data_containers import get_jwqldb_table_view_components
from .data_containers import thumbnails_ajax
from .data_containers import thumbnails_query_ajax
from .forms import InstrumentAnomalySubmitForm
from .forms import AnomalyQueryForm
from .forms import FileSearchForm


FILESYSTEM_DIR = os.path.join(get_config()['jwql_dir'], 'filesystem')


[docs]def anomaly_query(request): """The anomaly query form page""" form = AnomalyQueryForm(request.POST or None) 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]['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_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_anomalies[instrument] = query_configs[instrument]['anomalies'] anomaly_query_config.INSTRUMENTS_CHOSEN = form.cleaned_data['instrument'] anomaly_query_config.ANOMALIES_CHOSEN_FROM_CURRENT_ANOMALIES = all_anomalies anomaly_query_config.APERTURES_CHOSEN = all_apers anomaly_query_config.FILTERS_CHOSEN = all_filters anomaly_query_config.EXPTIME_MIN = str(form.cleaned_data['exp_time_min']) anomaly_query_config.EXPTIME_MAX = str(form.cleaned_data['exp_time_max']) anomaly_query_config.DETECTORS_CHOSEN = all_detectors anomaly_query_config.EXPTYPES_CHOSEN = all_exptypes anomaly_query_config.READPATTS_CHOSEN = all_readpatts anomaly_query_config.GRATINGS_CHOSEN = all_gratings return redirect('/query_submit') context = {'form': form, 'inst': ''} template = 'anomaly_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 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 """ # Ensure the instrument is correctly capitalized inst = JWST_INSTRUMENT_NAMES_MIXEDCASE[inst.lower()] # Get list of all files for the given instrument filenames_public = get_filenames_by_instrument(inst, restriction='public') filenames_proprietary = get_filenames_by_instrument(inst, restriction='proprietary') # Determine locations to the files filenames = [] for filename in filenames_public: try: relative_filepath = filesystem_path(filename, check_existence=False) full_filepath = os.path.join(FILESYSTEM_DIR, 'public', relative_filepath) filenames.append(full_filepath) except ValueError: print('Unable to determine filepath for {}'.format(filename)) for filename in filenames_proprietary: try: relative_filepath = filesystem_path(filename, check_existence=False) full_filepath = os.path.join(FILESYSTEM_DIR, 'proprietary', relative_filepath) filenames.append(full_filepath) except ValueError: print('Unable to determine filepath for {}'.format(filename)) # Gather information about the proposals for the given instrument proposal_info = get_proposal_info(filenames) context = {'inst': inst, 'all_filenames': filenames, 'num_proposals': proposal_info['num_proposals'], 'thumbnails': {'proposals': proposal_info['proposals'], 'thumbnail_paths': proposal_info['thumbnail_paths'], 'num_files': proposal_info['num_files']}} return JsonResponse(context, json_dumps_params={'indent': 2})
[docs]def archive_thumbnails(request, inst, proposal): """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 Returns ------- HttpResponse object Outgoing response sent to the webpage """ # Ensure the instrument is correctly capitalized inst = JWST_INSTRUMENT_NAMES_MIXEDCASE[inst.lower()] template = 'thumbnails.html' context = {'inst': inst, 'prop': proposal, 'base_url': get_base_url()} return render(request, template, context)
[docs]def archive_thumbnails_ajax(request, inst, proposal): """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 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) return JsonResponse(data, json_dumps_params={'indent': 2})
[docs]def archive_thumbnails_query_ajax(request): """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 Returns ------- JsonResponse object Outgoing response sent to the webpage """ # Ensure the instrument is correctly capitalized instruments_list = [] for instrument in anomaly_query_config.INSTRUMENTS_CHOSEN: instrument = JWST_INSTRUMENT_NAMES_MIXEDCASE[instrument.lower()] instruments_list.append(instrument) parameters = anomaly_query_config.PARAMETERS # when parameters only contains nirspec as instrument, thumbnails still end up being all niriss data thumbnails = get_thumbnails_all_instruments(parameters) anomaly_query_config.THUMBNAILS = thumbnails data = thumbnails_query_ajax(thumbnails) return JsonResponse(data, json_dumps_params={'indent': 2})
[docs]def dashboard(request): """Generate the dashbaord 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() 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], [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 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 """ if tablename_param is None: table_meta, tablename = get_jwqldb_table_view_components(request) else: table_meta = build_table(tablename_param) tablename = tablename_param _, _, engine, _ = load_connection(get_config()['connection_string']) all_jwql_tables = engine.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 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' parameters = {} parameters['instruments'] = anomaly_query_config.INSTRUMENTS_CHOSEN parameters['apertures'] = anomaly_query_config.APERTURES_CHOSEN parameters['filters'] = anomaly_query_config.FILTERS_CHOSEN parameters['detectors'] = anomaly_query_config.DETECTORS_CHOSEN parameters['exposure_types'] = anomaly_query_config.EXPTYPES_CHOSEN parameters['read_patterns'] = anomaly_query_config.READPATTS_CHOSEN parameters['gratings'] = anomaly_query_config.GRATINGS_CHOSEN parameters['anomalies'] = anomaly_query_config.ANOMALIES_CHOSEN_FROM_CURRENT_ANOMALIES anomaly_query_config.PARAMETERS = parameters context = {'inst': '', 'base_url': get_base_url() } 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): """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 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, 'header_info': get_header_info(filename)} return render(request, template, context)
[docs]def view_image(request, inst, file_root, rewrite=False): """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 rewrite : bool, optional Regenerate the jpg preview of `file` if it already exists? 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, rewrite) # Determine current flagged anomalies current_anomalies = get_current_flagged_anomalies(file_root, inst) # Create a form instance form = InstrumentAnomalySubmitForm(request.POST or None, instrument=inst.lower(), initial={'anomaly_choices': current_anomalies}) # If this is a POST request, process the form data if request.method == 'POST': anomaly_choices = dict(request.POST)['anomaly_choices'] if form.is_valid(): form.update_anomaly_table(file_root, 'unknown', anomaly_choices) # Build the context context = {'inst': inst, 'prop_id': file_root[2:7], 'file_root': file_root, 'jpg_files': image_info['all_jpegs'], 'fits_files': image_info['all_files'], 'suffixes': image_info['suffixes'], 'num_ints': image_info['num_ints'], 'form': form} return render(request, template, context)