diff --git a/.gitignore b/.gitignore index ff0e7b3d..d94b703f 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,9 @@ user_data/runs/* user_data/workflows/* user_data/external_data/* +user_data/settings/* +!user_data/settings/plots_default.yaml +!user_data/settings/databases_default.yaml user_data/debug/* ui/static/admin/* ui/uploads/* diff --git a/protzilla/constants/paths.py b/protzilla/constants/paths.py index 49364bda..177eef6c 100644 --- a/protzilla/constants/paths.py +++ b/protzilla/constants/paths.py @@ -4,6 +4,7 @@ USER_DATA_PATH = Path(PROJECT_PATH, "user_data") RUNS_PATH = USER_DATA_PATH / "runs" WORKFLOWS_PATH = USER_DATA_PATH / "workflows" +SETTINGS_PATH = USER_DATA_PATH / "settings" EXTERNAL_DATA_PATH = Path(PROJECT_PATH, "user_data/external_data") WORKFLOW_META_PATH = Path(PROJECT_PATH, "protzilla/constants/workflow_meta.json") UI_PATH = Path(PROJECT_PATH, "ui") diff --git a/protzilla/data_preprocessing/plots.py b/protzilla/data_preprocessing/plots.py index 2a487eb6..ef396e9e 100644 --- a/protzilla/data_preprocessing/plots.py +++ b/protzilla/data_preprocessing/plots.py @@ -7,7 +7,6 @@ from protzilla.data_preprocessing.plots_helper import generate_tics from protzilla.utilities import default_intensity_column -from protzilla.utilities.plot_template import * from protzilla.constants.colors import * def create_pie_plot( diff --git a/protzilla/steps.py b/protzilla/steps.py index eecf0d04..bf21942a 100644 --- a/protzilla/steps.py +++ b/protzilla/steps.py @@ -9,7 +9,8 @@ from pathlib import Path import pandas as pd -import plotly +import plotly.io as pio +import plotly.graph_objects as go from PIL import Image from protzilla.utilities import format_trace @@ -277,33 +278,47 @@ def __repr__(self): def empty(self) -> bool: return len(self.plots) == 0 - def export(self, format_): + def export(self, settings: dict) -> list: + """ + Converts all plots from this step to files according to the format and size in the Plotly template. + An exported plot is represented as BytesIO object containing binary image data. + :param settings: Dict containing the plot settings. + :return: List of all exported plots. + """ + from ui.settings.plot_template import get_scale_factor exports = [] + format_ = settings["file_format"] + for plot in self.plots: - if isinstance(plot, plotly.graph_objs.Figure): - if format_ in ["eps", "tiff"]: - png_binary = plotly.io.to_image(plot, format="png", scale=4) - img = Image.open(BytesIO(png_binary)).convert("RGB") + scale_factor = get_scale_factor(plot, settings) + # For Plotly GO Figure + if isinstance(plot, go.Figure): + if format_ in ["tiff", "eps"]: + binary_png = pio.to_image(plot, format="png", scale=scale_factor) + img = Image.open(BytesIO(binary_png)).convert("RGB") binary = BytesIO() if format_ == "tiff": img.save(binary, format="tiff", compression="tiff_lzw") - else: + elif format_ == "eps": img.save(binary, format=format_) + binary.seek(0) exports.append(binary) else: - binary_string = plotly.io.to_image(plot, format=format_, scale=4) - exports.append(BytesIO(binary_string)) + binary_png = pio.to_image(plot, format=format_, scale=scale_factor) + exports.append(BytesIO(binary_png)) elif isinstance(plot, dict) and "plot_base64" in plot: plot = plot["plot_base64"] - if isinstance(plot, bytes): # base64 encoded plots - if format_ in ["eps", "tiff"]: + # TO DO: Include scale_factor here + # For base64 encoded plot + if isinstance(plot, bytes): + if format_ in ["tiff", "eps"]: img = Image.open(BytesIO(base64.b64decode(plot))).convert("RGB") binary = BytesIO() if format_ == "tiff": img.save(binary, format="tiff", compression="tiff_lzw") - else: - img.save(binary, format=format_) + elif format_ == "eps": + img.save(binary, format="eps") binary.seek(0) exports.append(binary) elif format_ in ["png", "jpg"]: diff --git a/protzilla/utilities/plot_template.py b/protzilla/utilities/plot_template.py deleted file mode 100644 index 9661a41f..00000000 --- a/protzilla/utilities/plot_template.py +++ /dev/null @@ -1,34 +0,0 @@ -import plotly.io as pio -import plotly.graph_objects as go - -from protzilla.constants.colors import PLOT_PRIMARY_COLOR, PLOT_SECONDARY_COLOR - - -layout = go.Layout( - title={ - "font": { - "size": 16, - "family": "Arial" - }, - "y": 0.98, - "x": 0.5, - "xanchor": "center", - "yanchor": "top" - }, - font={ - "size": 14, - "family": "Arial" - }, - colorway=[PLOT_PRIMARY_COLOR, PLOT_SECONDARY_COLOR], - plot_bgcolor="white", - yaxis={ - "gridcolor": "lightgrey", - "zerolinecolor": "lightgrey" - }, - modebar={ - "remove": ["autoScale2d", "lasso", "lasso2d", "toImage", "select2d"], - }, - dragmode="pan" -) -pio.templates["plotly_protzilla"] = go.layout.Template(layout=layout) -pio.templates.default = "plotly_protzilla" \ No newline at end of file diff --git a/protzilla/utilities/utilities.py b/protzilla/utilities/utilities.py index 7ab9e000..0f4b7fe1 100644 --- a/protzilla/utilities/utilities.py +++ b/protzilla/utilities/utilities.py @@ -11,6 +11,7 @@ import pandas as pd import psutil +from django.http import QueryDict # recipie from https://docs.python.org/3/library/itertools.html def unique_justseen(iterable, key=None): @@ -136,3 +137,47 @@ def get_file_name_from_upload_path(upload_path: str) -> str: base_name = file_name_randomized.split("_")[0] file_extension = file_name_randomized.split(".")[-1] return f"{base_name}.{file_extension}" + + +def parameters_from_post(post: QueryDict) -> dict: + """ + Removes token from dict and converts the remaining entries into suitable data formats. + :param post: Django dict containing POST data. + :return: Dict containing the parameters in suitable formats. + """ + d = dict(post) + if "csrfmiddlewaretoken" in d: + del d["csrfmiddlewaretoken"] + parameters = {} + for k, v in d.items(): + if len(v) > 1: + # only used for named_output parameters and multiselect fields + parameters[k] = v + else: + parameters[k] = convert_str_if_possible(v[0]) + return parameters + + +def convert_str_if_possible(s): + """ + Converts an input value into suitable representation as a string. + :param s: Input value. + :return: Converted input value. + """ + try: + f = float(s) + return int(f) if int(f) == f else f + except ValueError: + if s == "checked": + # s is a checkbox + return True + if re.fullmatch(r"\d+(\.\d+)?(\|\d+(\.\d+)?)*", s): + # s is a multi-numeric input e.g. 1-0.12-5 + numbers_str = re.findall(r"\d+(?:\.\d+)?", s) + numbers = [] + for num in numbers_str: + num = float(num) + num = int(num) if int(num) == num else num + numbers.append(num) + return numbers + return s \ No newline at end of file diff --git a/tests/ui/test_settings.py b/tests/ui/test_settings.py new file mode 100644 index 00000000..de466a1c --- /dev/null +++ b/tests/ui/test_settings.py @@ -0,0 +1,41 @@ +import pytest +import plotly.graph_objects as go + +from protzilla.constants.colors import PLOT_PRIMARY_COLOR, PLOT_SECONDARY_COLOR +from protzilla.constants.paths import SETTINGS_PATH +from ui.settings.plot_template import ( + determine_font, + resize_for_display, + get_scale_factor +) + + +@pytest.fixture +def sample_params(): + return { + "section_id": "plots", + "file_format": "png", + "width": 85, + "height": 60, + "custom_font": "Ubuntu Mono", + "font": "Arial", + "heading_size": 11, + "text_size": 8 + } + +def test_determine_font(sample_params): + assert determine_font(sample_params) == "Arial" + sample_params["font"] = "Custom" + assert determine_font(sample_params) == "Ubuntu Mono" + +def test_resize_for_display(sample_params): + result = resize_for_display(sample_params) + assert result["display_width"] == 600 # SCALED_WIDTH + assert result["display_heading_size"] == 27 + +def test_get_scale_factor(sample_params): + fig = go.Figure() + fig.update_layout(width=600) + scale = get_scale_factor(fig, sample_params) + assert isinstance(scale, float) + assert round(scale, 3) == 1.673 \ No newline at end of file diff --git a/ui/main/settings.py b/ui/main/settings.py index 58cbaca9..e2a6e9f8 100644 --- a/ui/main/settings.py +++ b/ui/main/settings.py @@ -47,6 +47,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "runs", + "settings", ] MIDDLEWARE = [ @@ -127,7 +128,10 @@ STATIC_URL = "static/" -STATICFILES_DIRS = [BASE_DIR / "static"] +STATICFILES_DIRS = [ + BASE_DIR / "static", + BASE_DIR / "settings/static" + ] # Default primary key field type # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field diff --git a/ui/main/urls.py b/ui/main/urls.py index 54726696..0e2c0854 100644 --- a/ui/main/urls.py +++ b/ui/main/urls.py @@ -21,6 +21,7 @@ urlpatterns = [ path("", views.index), path("runs/", include("runs.urls")), + path("settings/", include("settings.urls")), path("databases", views.databases, name="databases"), path("databases/upload", views.database_upload, name="database_upload"), path("databases/delete", views.database_delete, name="database_delete"), diff --git a/ui/main/views.py b/ui/main/views.py index 14e36465..da0892d3 100644 --- a/ui/main/views.py +++ b/ui/main/views.py @@ -19,6 +19,7 @@ def index(request): def databases(request): + request.session['last_view'] = "databases" databases = uniprot_databases() df_infos = {} if database_metadata_path.exists(): diff --git a/ui/runs/static/runs/style.css b/ui/runs/static/runs/style.css index ff1335d8..b75b144a 100644 --- a/ui/runs/static/runs/style.css +++ b/ui/runs/static/runs/style.css @@ -88,4 +88,10 @@ html, body { padding: 10px 8%; margin-top: 10px; background-color: rgb(255, 255, 255); +} +.plot-wrapper { + width: fit-content; + height: fit-content; + border: 1px solid #e8edf3; + border-radius: .375rem; } \ No newline at end of file diff --git a/ui/runs/templates/runs/details.html b/ui/runs/templates/runs/details.html index e60a42d6..e2452e98 100644 --- a/ui/runs/templates/runs/details.html +++ b/ui/runs/templates/runs/details.html @@ -215,7 +215,7 @@

{{ display_name }}

{% if current_plots %}
{% for plot in current_plots %} -
+
{{ plot|safe }}
{% endfor %} @@ -225,15 +225,13 @@

{{ display_name }}

{% if current_plots %}
- - +
{% endif %}
@@ -278,11 +276,11 @@
- {% endif %} {% if not last_step %} diff --git a/ui/runs/views.py b/ui/runs/views.py index 56d0d264..accf7bdd 100644 --- a/ui/runs/views.py +++ b/ui/runs/views.py @@ -29,6 +29,7 @@ format_trace, get_memory_usage, name_to_title, + parameters_from_post, ) from protzilla.workflow import get_available_workflow_names from protzilla.constants.paths import WORKFLOWS_PATH @@ -38,13 +39,14 @@ make_name_field, make_sidebar, ) -from ui.runs.views_helper import display_message, display_messages, parameters_from_post +from ui.runs.views_helper import display_message, display_messages from .form_mapping import ( get_empty_plot_form_by_method, get_filled_form_by_method, get_filled_form_by_request, ) +from ui.settings.views import load_settings active_runs: dict[str, Run] = {} @@ -68,6 +70,9 @@ def detail(request: HttpRequest, run_name: str): active_runs[run_name] = Run(run_name) run: Run = active_runs[run_name] + request.session['last_view'] = "runs:detail" + request.session['run_name'] = run_name + # section, step, method = run.current_run_location() # end_of_run = not step @@ -173,6 +178,9 @@ def index(request: HttpRequest, index_error: bool = False): :return: the rendered index page :rtype: HttpResponse """ + + request.session['last_view'] = "runs:index" + return render( request, "runs/index.html", @@ -447,11 +455,14 @@ def download_plots(request: HttpRequest, run_name: str): if run_name not in active_runs: active_runs[run_name] = Run(run_name) run = active_runs[run_name] - format_ = request.GET["format"] + settings = load_settings("plots") + format_ = settings["file_format"] index = run.steps.current_step_index section = run.current_step.section operation = run.current_step.operation - exported = run.current_plots.export(format_=format_) + exported = run.current_plots.export(settings) + if len(exported) == 0: + raise RuntimeError("List of exported plots is empty.") if len(exported) == 1: filename = f"{index}-{section}-{operation}.{format_}" return FileResponse(exported[0], filename=filename, as_attachment=True) diff --git a/ui/runs/views_helper.py b/ui/runs/views_helper.py index b4a640f9..3beb9edf 100644 --- a/ui/runs/views_helper.py +++ b/ui/runs/views_helper.py @@ -8,40 +8,6 @@ from ui.runs.utilities.alert import build_trace_alert -def parameters_from_post(post): - d = dict(post) - if "csrfmiddlewaretoken" in d: - del d["csrfmiddlewaretoken"] - parameters = {} - for k, v in d.items(): - if len(v) > 1: - # only used for named_output parameters and multiselect fields - parameters[k] = v - else: - parameters[k] = convert_str_if_possible(v[0]) - return parameters - - -def convert_str_if_possible(s): - try: - f = float(s) - return int(f) if int(f) == f else f - except ValueError: - if s == "checked": - # s is a checkbox - return True - if re.fullmatch(r"\d+(\.\d+)?(\|\d+(\.\d+)?)*", s): - # s is a multi-numeric input e.g. 1-0.12-5 - numbers_str = re.findall(r"\d+(?:\.\d+)?", s) - numbers = [] - for num in numbers_str: - num = float(num) - num = int(num) if int(num) == num else num - numbers.append(num) - return numbers - return s - - def get_displayed_steps( steps: StepManager, ) -> list[dict]: # TODO i think this broke with the new naming scheme, should be redone diff --git a/ui/settings/apps.py b/ui/settings/apps.py new file mode 100644 index 00000000..2b122eed --- /dev/null +++ b/ui/settings/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class SettingsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "settings" \ No newline at end of file diff --git a/ui/settings/plot_template.py b/ui/settings/plot_template.py new file mode 100644 index 00000000..d8414c4c --- /dev/null +++ b/ui/settings/plot_template.py @@ -0,0 +1,155 @@ +import plotly.io as pio +import plotly.graph_objects as go +import math + +from protzilla.disk_operator import YamlOperator +from protzilla.constants.colors import PLOT_PRIMARY_COLOR, PLOT_SECONDARY_COLOR +from protzilla.constants.paths import SETTINGS_PATH + + +SCALED_WIDTH = 600 +PT_TO_INCH = 1 / 72 +INCH_TO_MM = 25.4 +DPI = 300 + +template = None + +def load_settings(section_id: str) -> dict: + """ + Loads the stored settings for a given settings section. + :param section_id: The ID of the section that should be loaded. + :return: Dict containing the loaded settings for given section. + """ + op = YamlOperator() + path = SETTINGS_PATH / (section_id + ".yaml") + if path.exists(): + settings = op.read(path) + else: + default_path = SETTINGS_PATH / (section_id + "_default.yaml") + settings = op.read(default_path) + save_settings(settings, section_id) + return settings + +def save_settings(params: dict, section_id: str): + """ + Writes settings into settings section file. + :param params: Dict with parameter and values from this settings section. + :param section_id: The ID of the section. + """ + op = YamlOperator() + path = SETTINGS_PATH / (section_id + ".yaml") + op.write(path, params) + if section_id == "plots" and isinstance(template, PlotTemplate): + template.update(params) + template.apply() + +def determine_font(params: dict) -> str: + """ + Returns the selected or a given custom font. + :param params: Dict with parameter and values from this settings section. + :return: Selected font. + """ + if(params["font"] == "Custom"): + font = params["custom_font"] + else: + font = params["font"] + return font + +def resize_for_display(params: dict) -> dict: + """ + Scales the input sizes to sizes that can be easily displayed in a webbrowser. + :param params: Dict containing the plot settings. + :return: Dict containing plot settings with scaled sizes. + """ + # Figure size + ratio = params["width"] / params["height"] + display_height = int(SCALED_WIDTH / ratio) + + # Font size + ratio = SCALED_WIDTH / params["width"] + display_heading = int(params["heading_size"] * PT_TO_INCH * INCH_TO_MM * ratio) + display_text = int(params["text_size"] * PT_TO_INCH * INCH_TO_MM * ratio) + + params["display_width"] = SCALED_WIDTH + params["display_height"] = display_height + params["display_heading_size"] = display_heading + params["display_text_size"] = display_text + + return params + +def get_scale_factor( + fig: go.Figure, + params: dict + ) -> float: + """ + Calculates the scale factor for downloading the plot in desired size and resolution. + :param fig: Plotly figure to be scaled. + :param params: Dict containing the plot settings. + :return: Scale factor to scale the whole plot to desired size. + """ + current_width = fig.layout.width or SCALED_WIDTH + scale_factor = (params["width"] / INCH_TO_MM * DPI) / current_width + + return scale_factor + +class PlotTemplate: + def __init__(self): + params = resize_for_display(load_settings("plots")) + font = determine_font(params) + self.layout = go.Layout( + title={ + "font": { + "size": params["display_heading_size"], + "family": font + }, + "y": 0.95, + "x": 0.5, + "xanchor": "center", + "yanchor": "top" + }, + font={ + "size": params["display_text_size"], + "family": font + }, + colorway=[PLOT_PRIMARY_COLOR, PLOT_SECONDARY_COLOR], + plot_bgcolor="white", + yaxis={ + "gridcolor": "lightgrey", + "zerolinecolor": "lightgrey" + }, + modebar={ + "remove": ["autoScale2d", "lasso", "lasso2d", "toImage", "select2d"], + }, + dragmode="pan", + height=params["display_height"], + width=params["display_width"], + margin={ + "t": 50, + "b": 50 + } + ) + + def update(self, params: dict): + """ + Updates all relevant parameters of this Plotly template. + :param params: Dict containing properties of the Plotly template. + """ + params = resize_for_display(params) + font = determine_font(params) + self.layout.title.font.family = font + self.layout.font.family = font + + self.layout.height = params["display_height"] + self.layout.width = params["display_width"] + self.layout.title.font.size = params["display_heading_size"] + self.layout.font.size = params["display_text_size"] + + def apply(self): + """ + Applies the current template as default template. + """ + pio.templates["plotly_protzilla"] = go.layout.Template(layout=self.layout) + +template = PlotTemplate() +template.apply() +pio.templates.default = "plotly_protzilla" \ No newline at end of file diff --git a/ui/settings/static/settings.js b/ui/settings/static/settings.js new file mode 100644 index 00000000..a460a278 --- /dev/null +++ b/ui/settings/static/settings.js @@ -0,0 +1,27 @@ +$( document ).ready(function () { + let formHasChanged = false; + const sectionId = document.getElementById('settings-form').dataset.sectionId; + + $("#settings-form :input").on("change input", function () { + formHasChanged = true; + }); + + $("#cancel, #settings-icon").click(function (e) { + if(formHasChanged) { + e.preventDefault(); + $("#unsavedChangesModal").modal("show"); + } + }); + + $("#customFontInput").click(function () { + $("#customFontRadio").prop("checked", true); + }); + + $(".action-button").each(function () { + $(this).click(function () { + const form = document.getElementById("settings-form"); + form.action = $(this).data("action"); + form.submit(); + }); + }); +}); \ No newline at end of file diff --git a/ui/settings/static/settings_style.css b/ui/settings/static/settings_style.css new file mode 100644 index 00000000..9e616f4c --- /dev/null +++ b/ui/settings/static/settings_style.css @@ -0,0 +1,50 @@ +.wrapper { + display: flex; + width: 100%; + align-items: stretch; +} +.sidebar { + width: 320px; + height: 100vh; +} +.selected { + background-color: #E8EDF3 !important; +} +.btn-section { + background-color: white; + width: 300px !important; + height: 60px; + align-items: center; +} +.content { + padding-top: 20px; + flex-grow: 1; + display: flex; + justify-content: center; +} +.footer { + position: sticky; + bottom: 0; + padding: 10px 8%; + margin-top: 10px; + background-color: rgb(255, 255, 255); +} +.custom-font { + justify-content: flex-start; + align-items: center; + gap: 20px; +} +#customFontInput.form-control { + display: inline-block !important; + width: 400px; + margin: 10px; +} +.plot-wrapper { + width: fit-content; + height: fit-content; + border: 1px solid #e8edf3; + border-radius: .375rem; +} +.field-wrapper { + margin-bottom: 10px; +} \ No newline at end of file diff --git a/ui/settings/templates/settings_databases.html b/ui/settings/templates/settings_databases.html new file mode 100644 index 00000000..cfe86d8c --- /dev/null +++ b/ui/settings/templates/settings_databases.html @@ -0,0 +1,52 @@ +{% extends 'base.html' %} +{% load static %} +{# params: initials, sidebar, section_id, sections #} + +{% block css %} + +{% endblock %} + +{% block js %} + + +{% endblock %} + +{% block title %} + PROTzilla - Settings +{% endblock %} + +{% block content %} +
+ {{ sidebar|safe }} +
+
+

Manage Databases

+
In this section, you can manage your UniProt integration by uploading protein + data tables to enabling gene mapping.
+ +
+ {% csrf_token %} + + + +

Please note: This page is an placeholder for displaying the database management in the future.

+ +
+
Datebases
+
+ + +
+
+ {% block footer %} + {% include "settings_footer.html" %} + {% endblock %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/ui/settings/templates/settings_footer.html b/ui/settings/templates/settings_footer.html new file mode 100644 index 00000000..53fdc3ea --- /dev/null +++ b/ui/settings/templates/settings_footer.html @@ -0,0 +1,34 @@ +{% load static %} + +{% block css %} + +{% endblock %} + +{% block content %} + + +{% endblock %} \ No newline at end of file diff --git a/ui/settings/templates/settings_plots.html b/ui/settings/templates/settings_plots.html new file mode 100644 index 00000000..2773b357 --- /dev/null +++ b/ui/settings/templates/settings_plots.html @@ -0,0 +1,138 @@ +{% extends 'base.html' %} +{% load static %} +{# params: initials, sidebar, plot, sections, section_id #} + +{% block css %} +{% endblock %} + +{% block js %} + + + + +{% endblock %} + +{% block title %} + PROTzilla - Settings +{% endblock %} + +{% block content %} +
+ {{ sidebar|safe }} +
+
+

Configurations for Plots

+
+ Configurations for plots can be entered here. This information is automatically applied to all plots in PROTzilla. +
+
+ {% csrf_token %} + +
+ +
+ +
+
Downloading Plots
+
+ + +
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+ +
+
Texts
+
Font:
+
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+
+ + +
+
+
+
+
+ + +
+
+ + +
+
+
+
+
+ +
+
+ {{ plot|safe }} +
+ +
+
+ {% block footer %} + {% include "settings_footer.html" %} + {% endblock %} +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/ui/settings/templates/settings_sidebar.html b/ui/settings/templates/settings_sidebar.html new file mode 100644 index 00000000..68df5b56 --- /dev/null +++ b/ui/settings/templates/settings_sidebar.html @@ -0,0 +1,38 @@ +{% load static %} +{# params: sections, selected_section #} + +{% block css %} + +{% endblock %} + +{% block js %} +{% endblock %} + +{% block title %} + PROTzilla - Settings +{% endblock %} + +{% block content %} + +{% endblock %} \ No newline at end of file diff --git a/ui/settings/urls.py b/ui/settings/urls.py new file mode 100644 index 00000000..2fbd9a6a --- /dev/null +++ b/ui/settings/urls.py @@ -0,0 +1,14 @@ +from django.contrib import admin +from django.urls import include, path + +from . import views + +app_name = "settings" +urlpatterns = [ + path("runs/", include("runs.urls")), + path("save", views.save, name="save"), + path("last_view", views.last_view, name="last_view"), + path("plots", views.settings_plots, name="settings_plots"), + path("update_plot_preview", views.update_plot_preview, name="update_plot_preview"), + path("databases", views.settings_databases, name="settings_databases") +] \ No newline at end of file diff --git a/ui/settings/views.py b/ui/settings/views.py new file mode 100644 index 00000000..7f686ba5 --- /dev/null +++ b/ui/settings/views.py @@ -0,0 +1,119 @@ +import pandas + +import plotly.io as pio +import plotly.graph_objects as go + +from django.contrib import messages +from django.http import HttpResponseRedirect +from django.shortcuts import redirect, render +from django.urls import reverse, NoReverseMatch +from django.template.loader import render_to_string + +from protzilla.utilities.utilities import parameters_from_post +from protzilla.data_preprocessing.plots import create_bar_plot +from ui.settings.plot_template import template, load_settings, save_settings + + +SECTIONS = [ + { + "id": "plots", + "name": "Plot Configurations" + }, + { + "id": "databases", + "name": "Manage Databases" + }, +] + + +def make_sidebar(request, section_id): + sidebar_template = "settings_sidebar.html" + return render_to_string( + sidebar_template, + context=dict( + sections=SECTIONS, + selected_section=section_id + ) + ) + + +def settings_plots(request): + settings_content = load_settings("plots") + sidebar = make_sidebar(request, "plots") + plot = make_preview_plot(settings_content) + return render( + request, + "settings_plots.html", + context=dict( + initials=settings_content, + sidebar=sidebar, + plot=plot, + sections=SECTIONS, + section_id="plots" + ) + ) + + +def make_preview_plot(params: dict): + template.update(params) + fig = create_bar_plot( + ["Example 1", "Example 2"], + [0.7, 0.3], + "Example plot", + "Example", + "Example" + ) + pio.templates["plotly_protzilla_preview"] = go.layout.Template(layout=template.layout) + fig.update_layout(template="plotly_protzilla_preview") + plot = fig.to_html(include_plotlyjs=False, full_html=False) + return plot + + +def update_plot_preview(request): + params = parameters_from_post(request.POST) + sidebar = make_sidebar(request, "plots") + plot = make_preview_plot(params) + return render( + request, + "settings_plots.html", + context=dict( + initials=params, + sidebar=sidebar, + plot=plot, + section_id="plots" + ) + ) + + +def settings_databases(request): + settings_content = load_settings("databases") + sidebar = make_sidebar(request, "databases") + return render( + request, + "settings_databases.html", + context=dict( + initials=settings_content, + sidebar=sidebar, + sections=SECTIONS, + section_id="databases" + ) + ) + + +def save(request): + params = parameters_from_post(request.POST) + section_id = request.POST.get("section_id") + save_settings(params, section_id) + return HttpResponseRedirect(reverse("settings:last_view")) + + +def last_view(request): + view_name = request.session['last_view'] + try: + if view_name=="runs:detail": + run_name = request.session['run_name'] + return HttpResponseRedirect(reverse(view_name, args=(run_name,))) + else: + return HttpResponseRedirect(reverse(view_name)) + except NoReverseMatch: + return HttpResponseRedirect(reverse("runs:index")) \ No newline at end of file diff --git a/ui/static/templates/navbar.html b/ui/static/templates/navbar.html index e85afdff..13282165 100644 --- a/ui/static/templates/navbar.html +++ b/ui/static/templates/navbar.html @@ -16,6 +16,11 @@ Logo + + + + + \ No newline at end of file diff --git a/user_data/settings/databases_default.yaml b/user_data/settings/databases_default.yaml new file mode 100644 index 00000000..b07f1f9a --- /dev/null +++ b/user_data/settings/databases_default.yaml @@ -0,0 +1,2 @@ +section_id: databases +selected_database: Database 1 diff --git a/user_data/settings/plots_default.yaml b/user_data/settings/plots_default.yaml new file mode 100644 index 00000000..a15e2b2c --- /dev/null +++ b/user_data/settings/plots_default.yaml @@ -0,0 +1,8 @@ +custom_font: '' +file_format: png +font: Arial +heading_size: 11 +height: 60 +section_id: plots +text_size: 8 +width: 85