diff --git a/usaon_vta_survey/routes/__init__.py b/usaon_vta_survey/routes/__init__.py index 208e81d7..990c4a07 100644 --- a/usaon_vta_survey/routes/__init__.py +++ b/usaon_vta_survey/routes/__init__.py @@ -3,6 +3,7 @@ import usaon_vta_survey.routes.response.data_products import usaon_vta_survey.routes.response.observing_systems import usaon_vta_survey.routes.response.relationships.data_product_application +import usaon_vta_survey.routes.response.relationships.observing_system_data_product import usaon_vta_survey.routes.login import usaon_vta_survey.routes.logout import usaon_vta_survey.routes.user diff --git a/usaon_vta_survey/routes/response/relationships/data_product_application.py b/usaon_vta_survey/routes/response/relationships/data_product_application.py index 1ec36206..b05a86af 100644 --- a/usaon_vta_survey/routes/response/relationships/data_product_application.py +++ b/usaon_vta_survey/routes/response/relationships/data_product_application.py @@ -56,10 +56,7 @@ def _response_data_product( data_product_id: int | None, response_id: int, ) -> ResponseDataProduct: - """Return a data product db object (or 404), and do some mutations. - - TODO: Extract mutations to another function responsible for that. - """ + """Return a data product db object (or 404).""" if data_product_id is not None: response_data_product = db.get_or_404(ResponseDataProduct, data_product_id) else: @@ -73,7 +70,7 @@ def _response_application( application_id: int | None, response_id: int, ) -> ResponseApplication: - """Return an application db object (or 404), and do some mutations.""" + """Return an application db object (or 404).""" if application_id is not None: response_application = db.get_or_404(ResponseApplication, application_id) else: diff --git a/usaon_vta_survey/routes/response/relationships/observing_system_data_product.py b/usaon_vta_survey/routes/response/relationships/observing_system_data_product.py new file mode 100644 index 00000000..ce7a646a --- /dev/null +++ b/usaon_vta_survey/routes/response/relationships/observing_system_data_product.py @@ -0,0 +1,219 @@ +from flask import Request, redirect, render_template, request, url_for +from flask_wtf import FlaskForm +from wtforms import FormField + +from usaon_vta_survey import app, db +from usaon_vta_survey.forms import FORMS_BY_MODEL +from usaon_vta_survey.models.tables import ( + ResponseDataProduct, + ResponseObservingSystem, + ResponseObservingSystemDataProduct, + Survey, +) +from usaon_vta_survey.util.authorization import limit_response_editors + + +def _update_super_form( + super_form: type[FlaskForm], + /, + *, + data_product_id: int | None, + observing_system_id: int | None, +) -> None: + """Populate the form of forms with sub-forms depending on provided IDs. + + When an ID for an object is not provided, we need to gather information from the + user to create that object. + + TODO: Better function name. + """ + if observing_system_id is None: + super_form.observing_system = FormField(FORMS_BY_MODEL[ResponseObservingSystem]) + + if data_product_id is None: + super_form.data_product = FormField(FORMS_BY_MODEL[ResponseDataProduct]) + + +def _update_relationship( + relationship: ResponseObservingSystemDataProduct, + *, + observing_system_id: int | None, + data_product_id: int | None, +) -> None: + """Populate the relationship with any known identifiers. + + TODO: Better function name. + """ + if observing_system_id: + relationship.response_observing_system_id = observing_system_id + + if data_product_id: + relationship.response_data_product_id = data_product_id + + +# may not need to be internal +def _response_data_product( + *, + data_product_id: int | None, + response_id: int, +) -> ResponseDataProduct: + """Return a data product db object (or 404).""" + if data_product_id is not None: + response_data_product = db.get_or_404(ResponseDataProduct, data_product_id) + else: + response_data_product = ResponseDataProduct(response_id=response_id) + + return response_data_product + + +def _response_observing_system( + *, + observing_system_id: int | None, + response_id: int, +) -> ResponseObservingSystem: + """Return an observing system db object (or 404).""" + if observing_system_id is not None: + response_observing_system = db.get_or_404( + ResponseObservingSystem, observing_system_id + ) + else: + response_observing_system = ResponseObservingSystem(response_id=response_id) + + return response_observing_system + + +def _response_observing_system_data_product( + *, + observing_system_id: int | None, + data_product_id: int | None, +) -> ResponseObservingSystemDataProduct: + """Return a relationship db object. + + Returned object may be transient or persistent depending on whether a match exists + in the db. + """ + if data_product_id and observing_system_id: + # If not found, will be `None` + response_observing_system_data_product = db.session.get( + ResponseObservingSystemDataProduct, + (data_product_id, observing_system_id), + ) + else: + response_observing_system_data_product = None + + if response_observing_system_data_product is not None: + return response_observing_system_data_product + else: + return ResponseObservingSystemDataProduct() + + +def _request_args(request: Request) -> tuple[int | None, int | None]: + data_product_id: int | str | None = request.args.get('data_product_id') + if data_product_id is not None: + data_product_id = int(data_product_id) + + observing_system_id: int | str | None = request.args.get('observing_system_id') + if observing_system_id is not None: + observing_system_id = int(observing_system_id) + + return data_product_id, observing_system_id + + +@app.route( + '/response//observing_system_data_product_relationships', + methods=['GET', 'POST'], +) +def view_response_observing_system_data_product_relationships(survey_id: str): + """View and add observing system/dataproduct relationships to a response. + + TODO: Refactor this whole pile of stuff. Less string magic. Less cyclomatic + complexity. + """ + data_product_id, observing_system_id = _request_args(request) + survey = db.get_or_404(Survey, survey_id) + + class SuperForm(FlaskForm): + """Combine all necessary forms into one super-form. + + NOTE: Additional class attributes are added dynamically below. + """ + + relationship = FormField(FORMS_BY_MODEL[ResponseObservingSystemDataProduct]) + + response_observing_system_data_product = _response_observing_system_data_product( + data_product_id=data_product_id, + observing_system_id=observing_system_id, + ) + + response_data_product = _response_data_product( + data_product_id=data_product_id, + response_id=survey.response_id, + ) + + response_observing_system = _response_observing_system( + observing_system_id=observing_system_id, + response_id=survey.response_id, + ) + + _update_super_form( + SuperForm, + observing_system_id=observing_system_id, + data_product_id=data_product_id, + ) + _update_relationship( + response_observing_system_data_product, + observing_system_id=observing_system_id, + data_product_id=data_product_id, + ) + + form_obj: dict[ + str, + ResponseObservingSystem + | ResponseDataProduct + | ResponseObservingSystemDataProduct, + ] = { + 'observing_system': response_observing_system, + 'data_product': response_data_product, + # NOTE: Logic below depends on relationship being last in this dict + 'relationship': response_observing_system_data_product, + } + + if request.method == 'POST': + limit_response_editors() + form = SuperForm(request.form, obj=form_obj) + + if form.validate(): + # Add only submitted sub-forms into the db session + for key, obj in form_obj.items(): + if hasattr(form, key): + form[key].form.populate_obj(obj) + db.session.add(obj) + + # Update the relationship object with the ids of any new entities + if type(obj) is not ResponseObservingSystemDataProduct: + # Get the db object's new ID + db.session.flush() + db.session.refresh(obj) + + # Update the relationship db object + setattr( + response_observing_system_data_product, + f'response_{key}_id', + obj.id, + ) + + db.session.commit() + + return redirect(url_for('view_response_data_products', survey_id=survey.id)) + + form = SuperForm(obj=form_obj) + return render_template( + 'response/relationships/observing_system_data_product.html', + form=form, + survey=survey, + observing_system=response_observing_system, + observing_systems=survey.response.observing_systems, + data_product=response_data_product, + data_products=survey.response.data_products, + relationship=response_observing_system_data_product, + ) diff --git a/usaon_vta_survey/templates/macros/forms/misc.j2 b/usaon_vta_survey/templates/macros/forms/misc.j2 index 72a9e560..3bc596a5 100644 --- a/usaon_vta_survey/templates/macros/forms/misc.j2 +++ b/usaon_vta_survey/templates/macros/forms/misc.j2 @@ -113,45 +113,3 @@ {% endif %} {%- endmacro -%} - - -{% macro data_product_application_fields(form) -%} - {{form.criticality_rating.label}} {{form.criticality_rating(size=5)}} - {% if form.criticality_rating.errors %} - - {% endif %} -
- - {{form.performance_rating.label}} {{form.performance_rating(size=50)}} - {% if form.performance_rating.errors %} - - {% endif %} -
- - {{form.rationale.label}} {{form.rationale(size=50)}} - {% if form.rationale.errors %} - - {% endif %} -
- - {{form.needed_improvements.label}} {{form.needed_improvements(size=50)}} - {% if form.needed_improvements.errors %} - - {% endif %} -{%- endmacro -%} diff --git a/usaon_vta_survey/templates/macros/forms/relationships/app_sba.j2 b/usaon_vta_survey/templates/macros/forms/relationships/app_sba.j2 new file mode 100644 index 00000000..75692694 --- /dev/null +++ b/usaon_vta_survey/templates/macros/forms/relationships/app_sba.j2 @@ -0,0 +1,81 @@ +{% macro data_product_application_fields(form) -%} + {{form.criticality_rating.label}} {{form.criticality_rating(size=5)}} + {% if form.criticality_rating.errors %} + + {% endif %} +
+ + {{form.performance_rating.label}} {{form.performance_rating(size=50)}} + {% if form.performance_rating.errors %} + + {% endif %} +
+ + {{form.rationale.label}} {{form.rationale(size=50)}} + {% if form.rationale.errors %} + + {% endif %} +
+ + {{form.needed_improvements.label}} {{form.needed_improvements(size=50)}} + {% if form.needed_improvements.errors %} + + {% endif %} +{%- endmacro -%} + +{% macro observing_system_data_product_fields(form) -%} + {{form.criticality_rating.label}} {{form.criticality_rating(size=5)}} + {% if form.criticality_rating.errors %} + + {% endif %} +
+ + {{form.performance_rating.label}} {{form.performance_rating(size=50)}} + {% if form.performance_rating.errors %} + + {% endif %} +
+ + {{form.rationale.label}} {{form.rationale(size=50)}} + {% if form.rationale.errors %} + + {% endif %} +
+ + {{form.needed_improvements.label}} {{form.needed_improvements(size=50)}} + {% if form.needed_improvements.errors %} + + {% endif %} +{%- endmacro -%} diff --git a/usaon_vta_survey/templates/response/data_products.html b/usaon_vta_survey/templates/response/data_products.html index c28905fa..7b6f93d4 100644 --- a/usaon_vta_survey/templates/response/data_products.html +++ b/usaon_vta_survey/templates/response/data_products.html @@ -27,7 +27,13 @@

Data products


{% endfor %} - Add relationship + + Add relationship + {% endfor %} diff --git a/usaon_vta_survey/templates/response/observing_systems.html b/usaon_vta_survey/templates/response/observing_systems.html index 0a3d5cd7..531657ee 100644 --- a/usaon_vta_survey/templates/response/observing_systems.html +++ b/usaon_vta_survey/templates/response/observing_systems.html @@ -11,24 +11,10 @@

Observing systems

- {% for observing_system in response.observing_systems %} - {% endfor %}
NameObserving systems related
{{observing_system.name}} - {% if not observing_system.observing_system_relationships %} - None -
- {% endif %} - - {% for observing_system_relationship in observing_system.input_relationships %} - {{observing_system_relationship}} -
- {% endfor %} - - Add relationship -
diff --git a/usaon_vta_survey/templates/response/relationships/data_product_application.html b/usaon_vta_survey/templates/response/relationships/data_product_application.html index 03e35d4c..28ba0b12 100644 --- a/usaon_vta_survey/templates/response/relationships/data_product_application.html +++ b/usaon_vta_survey/templates/response/relationships/data_product_application.html @@ -4,6 +4,9 @@ data_product_fields, data_product_application_fields %} +{% from 'macros/forms/relationships/app_sba.j2' import + data_product_application_fields +%} {% block content %} diff --git a/usaon_vta_survey/templates/response/relationships/observing_system_data_product.html b/usaon_vta_survey/templates/response/relationships/observing_system_data_product.html new file mode 100644 index 00000000..485a8027 --- /dev/null +++ b/usaon_vta_survey/templates/response/relationships/observing_system_data_product.html @@ -0,0 +1,91 @@ +{% extends 'response/base.html' %} +{% from 'macros/forms/misc.j2' import + observing_system_fields, + data_product_fields, + observing_system_data_product_fields +%} +{% from 'macros/forms/relationships/app_sba.j2' import + observing_system_data_product_fields +%} + + +{% block content %} + + {{super()}} + +

Observing System <-> Data Product relationship

+ + +
+ +
+

Obserivng Systems

+ + +
+ + +
+

Data Product

+ + +
+
+ +
+ {{form.csrf_token}} + +
+
+ {% if sqla_inspect(observing_system).transient %} + {{ observing_system_fields(form.observing_system.form) }} + {% else %} +

Name: {{observing_system.name}}

+ {% endif %} +
+ +
+ {% if sqla_inspect(data_product).transient %} + {{ data_product_fields(form.data_product.form) }} + {% else %} +

Name: {{data_product.name}}

+ {% endif %} +
+
+ +

Relationship

+ {% if sqla_inspect(relationship).transient %} + {{ observing_system_data_product_fields(form.relationship.form) }} + {% else %} + {{relationship}} + {% endif %} + + +
+ + +{% endblock %}