From 4c38ddaa9a27eaaa0467cb9d3b8454e1cc91b0c9 Mon Sep 17 00:00:00 2001 From: jide Date: Wed, 30 Oct 2024 13:34:21 -0500 Subject: [PATCH] Add NSF Award Lookup functionality. --- webapp/home/home_utils.py | 2 +- webapp/home/templates/base.html | 2 +- webapp/home/templates/faq.html | 11 ++- webapp/home/templates/news.html | 11 +++ webapp/static/help.txt | 12 ++++ webapp/views/project/project.py | 85 ++++++++++++++++++---- webapp/views/project/templates/award.html | 86 +++++++++++++++++------ 7 files changed, 172 insertions(+), 37 deletions(-) diff --git a/webapp/home/home_utils.py b/webapp/home/home_utils.py index b073cd82..a49cc4f1 100644 --- a/webapp/home/home_utils.py +++ b/webapp/home/home_utils.py @@ -15,7 +15,7 @@ from metapype.model.node import Node -RELEASE_NUMBER = '2024.10.09' +RELEASE_NUMBER = '2024.10.30' def extract_caller_module_name(): diff --git a/webapp/home/templates/base.html b/webapp/home/templates/base.html index 15c3a847..429d984a 100644 --- a/webapp/home/templates/base.html +++ b/webapp/home/templates/base.html @@ -11,7 +11,7 @@ {% import '_macros.html' as macros %} {# The following must agree with RELEASE_NUMBER in home_utils.py #} -{% set release_number = '2024.10.09' %} +{% set release_number = '2024.10.30' %} {% set optional = 'Black' %} {% block head %} diff --git a/webapp/home/templates/faq.html b/webapp/home/templates/faq.html index ee270cc2..8b533305 100644 --- a/webapp/home/templates/faq.html +++ b/webapp/home/templates/faq.html @@ -145,7 +145,7 @@

Frequently Asked Questions

-
How do I enter and preview Markdown?
+
How do I enter and preview Markdown and LaTeX?
The EML standard allows certain metadata elements, such as Abstract and Method Step Description, to include Markdown formatting.

In order to enter Markdown tags in ezEML, you need to enable "Complex Text Element Editing" on the Settings page, accessible via the EML Documents menu. For details on how complex text element editing works, see the associated Help @@ -161,7 +161,14 @@

Frequently Asked Questions

- Goodbye
\</markdown\>
\</abstract\> - +

+ To enter equations via LaTeX, enable Complex Text Element Editing and enter the LaTeX code. No need to use Markdown. For example:

+ +\<abstract\>
+Equation 1: $$VWC = -5.3 \times 10^{-2} + 2.92 \times 10^{-2}K_a - 5.5 \times 10^{-4}K_a^2 + 4.3 \times 10^{-6}K_a^3$$
+\</abstract\> +

+ As in the Markdown case, preview the rendering of the LaTeX code by using Preview Your Metadata in the EDI Data Portal in the Import/Export menu.
{#
#} diff --git a/webapp/home/templates/news.html b/webapp/home/templates/news.html index 1d45bb8e..1e819893 100644 --- a/webapp/home/templates/news.html +++ b/webapp/home/templates/news.html @@ -16,6 +16,17 @@

What's New

  + + 2024.10.30 + + Added NSF Funding Award Lookup
+ The Funding Award page for projects now includes an NSF Award Lookup button that retrieves the funding award's title from the National Science Foundation's public API. + + + +   + + 2024.10.09 diff --git a/webapp/static/help.txt b/webapp/static/help.txt index 33a675aa..30fcb4cf 100644 --- a/webapp/static/help.txt +++ b/webapp/static/help.txt @@ -135,6 +135,18 @@ The unique identifier used by the funder to uniquely identify an award. These are typically alphanumeric values that are unique within the system used by a given funder. The number should be listed using the canonical form that each funder uses to express its award numbers and should not be prefixed or postfixed with extra text such as the acronym of the funder or the name of the funder, which is available instead in the Funder Name field. -------------------- +award_lookup +NSF Award Lookup +If the funding award is from the National Science Foundation (NSF), you can enter the Award Number and click NSF Award Lookup to have ezEML look up the Award Title for you. This lets you check that the award number you've entered correctly matches the award you intended. + +The Award Number should be numeric. E.g., 1234567, not NSF-1234567 or #1234567. + +If the award number is found, the Award Title field will be filled in with the title of the award. If it turns out that the award is not the one you intended, you can always click Cancel to leave the page without saving the award information. + +Of course, you can manually enter the Award Title, instead of using NSF Award Lookup. + +If the Award Number field is empty, the NSF Award Lookup button will be disabled. +-------------------- award_title Award Title The title of the award or grant. This field is required. diff --git a/webapp/views/project/project.py b/webapp/views/project/project.py index 354d47a3..1099e8bd 100644 --- a/webapp/views/project/project.py +++ b/webapp/views/project/project.py @@ -3,6 +3,7 @@ """ import collections +import requests import daiquiri from flask import ( @@ -328,6 +329,42 @@ def funding_award_select_get(filename=None, form=None, project_node_id=None): form=form, help=help, related_project=related_project) +def get_award_title(award_number): + """ + Fetch the title of an NSF award given its award number. + + Args: + award_number (str): The NSF award number + + Returns: + str: The title of the award + + Raises: + requests.RequestException: If the API request fails + KeyError: If the response doesn't contain the expected data structure + """ + award_number = award_number.strip() + if not award_number or not award_number.isdigit(): + raise ValueError(f'Not a valid award number: "{award_number}"') + + url = f"http://api.nsf.gov/services/v1/awards/{award_number}.json" + + try: + response = requests.get(url) + response.raise_for_status() # Raise an exception for bad status codes + + data = response.json() + if not data["response"]["award"]: + raise ValueError(f'No award data found for award number "{award_number}"') + title = data["response"]["award"][0]["title"] + return title + + except requests.RequestException as e: + raise requests.RequestException(f"Failed to fetch award data: {e}") + except (KeyError, IndexError) as e: + raise KeyError(f"Failed to extract title from response: {e}") + + @proj_bp.route('/funding_award//', methods=['GET', 'POST']) @proj_bp.route('/funding_award///', methods=['GET', 'POST']) @login_required @@ -347,6 +384,8 @@ def funding_award(filename=None, node_id=None, project_node_id=None): else: project_node = Node.get_node_instance(project_node_id) + submit_type = None + if request.method == 'POST': form_value = request.form form_dict = form_value.to_dict(flat=False) @@ -357,12 +396,12 @@ def funding_award(filename=None, node_id=None, project_node_id=None): # if request.method == 'POST' and form.validate_on_submit(): if request.method == 'POST': - next_page = PAGE_FUNDING_AWARD_SELECT + next_page = handle_hidden_buttons(PAGE_FUNDING_AWARD_SELECT) - submit_type = None - if is_dirty_form(form): + if 'Lookup' in form_dict: + submit_type = 'Lookup' + elif 'OK' in form_dict or is_hidden_button(): submit_type = 'Save Changes' - # flash(f'submit_type: {submit_type}') if submit_type == 'Save Changes': funder_name = form.funder_name.data @@ -390,11 +429,25 @@ def funding_award(filename=None, node_id=None, project_node_id=None): save_both_formats(filename=filename, eml_node=eml_node) - url = select_post(filename, form, form_dict, - 'POST', PAGE_FUNDING_AWARD_SELECT, PAGE_PROJECT, - PAGE_FUNDING_AWARD_SELECT, PAGE_FUNDING_AWARD, - project_node_id=project_node_id, import_page=PAGE_IMPORT_PARTIES) - return redirect(url) + url = select_post(filename, form, form_dict, + 'POST', PAGE_FUNDING_AWARD_SELECT, PAGE_PROJECT, + next_page, PAGE_FUNDING_AWARD, + project_node_id=project_node_id, import_page=PAGE_IMPORT_PARTIES) + return redirect(url) + + if submit_type == 'Lookup': + award_number = form.award_number.data + try: + award_title = get_award_title(award_number) + form.award_title.data = award_title + form.funder_name.data = 'National Science Foundation (NSF)' + + except requests.RequestException as e: + flash(e, 'error') + except ValueError as e: + flash(e, 'error') + except KeyError as e: + flash(f"Failed to extract title from response: {e}", 'error') # Process GET if not project_node_id: @@ -404,7 +457,7 @@ def funding_award(filename=None, node_id=None, project_node_id=None): title = 'Related Project Funding Award' related_project = True - if node_id != '1': + if node_id != '1' and submit_type != 'Lookup': award_nodes = project_node.find_all_children(names.AWARD) if award_nodes: for award_node in award_nodes: @@ -414,18 +467,26 @@ def funding_award(filename=None, node_id=None, project_node_id=None): init_form_md5(form) + if form.award_title.data and form.funder_name.data: + lookup_confirm = 'If the lookup succeeds, the Funder Name and Award Title fields will be overwritten. OK to continue?' + elif form.award_title.data: + lookup_confirm = 'If the lookup succeeds, the Award Title field will be overwritten. OK to continue?' + else: + lookup_confirm = None set_current_page('project') help = [get_help('award'), get_help('funder_name'), get_help('award_title'), - get_help('funder_identifiers'), get_help('award_number'), + get_help('award_lookup'), + get_help('funder_identifiers'), get_help('award_url')] return render_template('award.html', title=title, form=form, help=help, - related_project=related_project) + related_project=related_project, + lookup_confirm=lookup_confirm) def populate_award_form(form: AwardForm, award_node: Node): diff --git a/webapp/views/project/templates/award.html b/webapp/views/project/templates/award.html index 3ebeccf4..05c9b66d 100644 --- a/webapp/views/project/templates/award.html +++ b/webapp/views/project/templates/award.html @@ -1,8 +1,4 @@ - - - - - {% extends "base.html" %} +{% extends "base.html" %} {% import 'bootstrap/wtf.html' as wtf %} {% block app_content %} @@ -16,13 +12,16 @@ {% set help_award_title_id, help_award_title_title, help_award_title_content = help[2] %} {% set help_award_title_btn = help_award_title_id ~ '_btn' %} {% set help_award_title_dialog = help_award_title_id ~ '_dialog' %} - {% set help_funder_identifiers_id, help_funder_identifiers_title, help_funder_identifiers_content = help[3] %} - {% set help_funder_identifiers_btn = help_funder_identifiers_id ~ '_btn' %} - {% set help_funder_identifiers_dialog = help_funder_identifiers_id ~ '_dialog' %} - {% set help_award_number_id, help_award_number_title, help_award_number_content = help[4] %} + {% set help_award_number_id, help_award_number_title, help_award_number_content = help[3] %} {% set help_award_number_btn = help_award_number_id ~ '_btn' %} {% set help_award_number_dialog = help_award_number_id ~ '_dialog' %} - {% set help_award_url_id, help_award_url_title, help_award_url_content = help[5] %} + {% set help_award_lookup_id, help_award_lookup_title, help_award_lookup_content = help[4] %} + {% set help_award_lookup_btn = help_award_lookup_id ~ '_btn' %} + {% set help_award_lookup_dialog = help_award_lookup_id ~ '_dialog' %} + {% set help_funder_identifiers_id, help_funder_identifiers_title, help_funder_identifiers_content = help[5] %} + {% set help_funder_identifiers_btn = help_funder_identifiers_id ~ '_btn' %} + {% set help_funder_identifiers_dialog = help_funder_identifiers_id ~ '_dialog' %} + {% set help_award_url_id, help_award_url_title, help_award_url_content = help[6] %} {% set help_award_url_btn = help_award_url_id ~ '_btn' %} {% set help_award_url_dialog = help_award_url_id ~ '_dialog' %} {% endif %} @@ -45,12 +44,30 @@ <h5>Enter the funding award information below:</h5> <td valign="middle" style="padding-top: 15px;">{{ macros.help_button(help_award_title_btn) }}</td> </table> <table> - <td>{{ wtf.form_field(form.funder_identifier, size=100) }}</td> - <td valign="middle" style="padding-top: 15px;">{{ macros.help_button(help_funder_identifiers_btn) }}</td> + <tr> + <td>{{wtf.form_field(form.award_number, size=70) }} + </td> + <td valign="middle" style="padding-top: 15px;">{{ macros.help_button(help_award_number_btn) }} + </td> + <td> + <table> + <td> + {% if lookup_confirm %} + <input class="btn btn-primary" id="nsf_award_lookup" style="margin-left: 25px;margin-top: 6px;" name="Lookup" type="submit" + formnovalidate="formnovalidate" value="NSF Award Lookup" + onclick="confirm('{{ lookup_confirm }}');"/></td> + {% else %} + <input class="btn btn-primary" id="nsf_award_lookup" style="margin-left: 25px;margin-top: 6px;" name="Lookup" type="submit" + formnovalidate="formnovalidate" value="NSF Award Lookup"/></td> + {% endif %} + <td valign="middle" style="padding-top: 15px;">{{ macros.help_button(help_award_lookup_btn) }}</td> + </table> + </td> + </tr> </table> <table> - <td>{{wtf.form_field(form.award_number, size=100) }}</td> - <td valign="middle" style="padding-top: 15px;">{{ macros.help_button(help_award_number_btn) }}</td> + <td>{{ wtf.form_field(form.funder_identifier, size=100) }}</td> + <td valign="middle" style="padding-top: 15px;">{{ macros.help_button(help_funder_identifiers_btn) }}</td> </table> <table> <td>{{ wtf.form_field(form.award_url, size=100) }}</td> @@ -59,6 +76,8 @@ <h5>Enter the funding award information below:</h5> <br/><br/> <input class="btn btn-primary" name="OK" type="submit" value="Save and Continue"/> <input class="btn btn-primary" name="Cancel" type="submit" formnovalidate="formnovalidate" value="Cancel"/> + <br/><br/> + <p></p> {{ macros.hidden_buttons() }} {{ form.csrf_token }} {{ wtf.form_field(form.md5) }} @@ -69,8 +88,9 @@ <h5>Enter the funding award information below:</h5> {{ macros.help_dialog(help_award_dialog, help_award_title, help_award_content) }} {{ macros.help_dialog(help_funder_name_dialog, help_funder_name_title, help_funder_name_content) }} {{ macros.help_dialog(help_award_title_dialog, help_award_title_title, help_award_title_content) }} - {{ macros.help_dialog(help_funder_identifiers_dialog, help_funder_identifiers_title, help_funder_identifiers_content) }} {{ macros.help_dialog(help_award_number_dialog, help_award_number_title, help_award_number_content) }} + {{ macros.help_dialog(help_award_lookup_dialog, help_award_lookup_title, help_award_lookup_content) }} + {{ macros.help_dialog(help_funder_identifiers_dialog, help_funder_identifiers_title, help_funder_identifiers_content) }} {{ macros.help_dialog(help_award_url_dialog, help_award_url_title, help_award_url_content) }} {% endblock %} @@ -86,13 +106,16 @@ <h5>Enter the funding award information below:</h5> {% set help_award_title_id, help_award_title_title, help_award_title_content = help[2] %} {% set help_award_title_btn = help_award_title_id ~ '_btn' %} {% set help_award_title_dialog = help_award_title_id ~ '_dialog' %} - {% set help_funder_identifiers_id, help_funder_identifiers_title, help_funder_identifiers_content = help[3] %} - {% set help_funder_identifiers_btn = help_funder_identifiers_id ~ '_btn' %} - {% set help_funder_identifiers_dialog = help_funder_identifiers_id ~ '_dialog' %} - {% set help_award_number_id, help_award_number_title, help_award_number_content = help[4] %} + {% set help_award_number_id, help_award_number_title, help_award_number_content = help[3] %} {% set help_award_number_btn = help_award_number_id ~ '_btn' %} {% set help_award_number_dialog = help_award_number_id ~ '_dialog' %} - {% set help_award_url_id, help_award_url_title, help_award_url_content = help[5] %} + {% set help_award_lookup_id, help_award_lookup_title, help_award_lookup_content = help[4] %} + {% set help_award_lookup_btn = help_award_lookup_id ~ '_btn' %} + {% set help_award_lookup_dialog = help_award_lookup_id ~ '_dialog' %} + {% set help_funder_identifiers_id, help_funder_identifiers_title, help_funder_identifiers_content = help[5] %} + {% set help_funder_identifiers_btn = help_funder_identifiers_id ~ '_btn' %} + {% set help_funder_identifiers_dialog = help_funder_identifiers_id ~ '_dialog' %} + {% set help_award_url_id, help_award_url_title, help_award_url_content = help[6] %} {% set help_award_url_btn = help_award_url_id ~ '_btn' %} {% set help_award_url_dialog = help_award_url_id ~ '_dialog' %} {% endif %} @@ -102,9 +125,30 @@ <h5>Enter the funding award information below:</h5> {{ macros.help_script(help_award_dialog, help_award_btn) }} {{ macros.help_script(help_funder_name_dialog, help_funder_name_btn) }} {{ macros.help_script(help_award_title_dialog, help_award_title_btn) }} - {{ macros.help_script(help_funder_identifiers_dialog, help_funder_identifiers_btn) }} {{ macros.help_script(help_award_number_dialog, help_award_number_btn) }} + {{ macros.help_script(help_award_lookup_dialog, help_award_lookup_btn) }} + {{ macros.help_script(help_funder_identifiers_dialog, help_funder_identifiers_btn) }} {{ macros.help_script(help_award_url_dialog, help_award_url_btn) }} }); </script> + + <script> + document.addEventListener("DOMContentLoaded", function () { + const awardInput = document.getElementById('award_number'); + const lookupButton = document.getElementById('nsf_award_lookup'); + + function toggleButtonState() { + const inputValue = awardInput.value.trim(); // Remove whitespace + lookupButton.disabled = inputValue === ""; // Enable if not empty + } + + // Initialize button state on page load + toggleButtonState(); + + // Listen for input changes + awardInput.addEventListener('input', toggleButtonState); + }); + + </script> + {% endblock %} \ No newline at end of file