Skip to content

Commit

Permalink
update desktop home layout (#55)
Browse files Browse the repository at this point in the history
* update version footers

* add model class label to filters

* set color labels

* add and format

* remove bmds version number from input and validation

* show version in bmds desktop table

* fix rendering of collections and form bugs if errors

* validate bad colors and fix error message styling

* show bmds-ui version instead of pybmds version
  • Loading branch information
shapiromatron authored Aug 3, 2024
1 parent bfbc081 commit 48776dd
Show file tree
Hide file tree
Showing 24 changed files with 221 additions and 135 deletions.
6 changes: 6 additions & 0 deletions bmds_ui/analysis/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
model_types = {
"D": "Dichotomous",
"C": "Continuous",
"ND": "Nested Dichotomous",
"MT": "Multistage Cancer / Multitumor",
}
9 changes: 7 additions & 2 deletions bmds_ui/analysis/forms.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
from django import forms
from django.forms.widgets import TextInput

from . import models


class ColorInput(TextInput):
input_type = "color"


class CreateAnalysisForm(forms.ModelForm):
class Meta:
model = models.Analysis
Expand All @@ -16,10 +21,10 @@ def save(self, commit=True):
class CollectionForm(forms.ModelForm):
def __init__(self, *args, **kw):
super().__init__(*args, **kw)
self.fields["name"].widget.attrs["placeholder"] = "Label"
for field in self.fields.values():
field.widget.attrs["class"] = "form-control"

class Meta:
model = models.Collection
fields = ("name", "description")
fields = ("name", "bg_color", "description")
widgets = {"bg_color": ColorInput()} # noqa: RUF012
27 changes: 27 additions & 0 deletions bmds_ui/analysis/migrations/0005_collection_bg_color.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 5.0.6 on 2024-07-27 00:35
import re

import django
from django.db import migrations, models


class Migration(migrations.Migration):
dependencies = [
("analysis", "0004_collection_analysis_last_updated_analysis_starred_and_more"),
]

operations = [
migrations.AddField(
model_name="collection",
name="bg_color",
field=models.CharField(
default="#17A2B8",
max_length=7,
validators=[
django.core.validators.RegexValidator(
regex=re.compile("^#(?:[0-9a-fA-F]{3}){1,2}$")
)
],
),
),
]
24 changes: 18 additions & 6 deletions bmds_ui/analysis/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
from io import BytesIO

import pandas as pd
import pydantic
import reversion
from django.conf import settings
from django.core.cache import cache
from django.core.exceptions import ValidationError
from django.core.validators import RegexValidator
from django.db import DataError, models
from django.urls import reverse
from django.utils.text import slugify
Expand All @@ -21,13 +23,14 @@
from pybmds.recommender.recommender import RecommenderSettings
from pybmds.types.session import VersionSchema

from .. import __version__
from ..common.utils import random_string
from . import tasks, validators
from . import constants, tasks, validators
from .executor import AnalysisSession, MultiTumorSession, Session, deserialize
from .reporting import excel
from .reporting.cache import DocxReportCache, ExcelReportCache
from .schema import AnalysisOutput, AnalysisSessionSchema
from .validators.session import BmdsVersion
from .utils import re_hex_color

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -157,6 +160,10 @@ def maybe_hanging(cls, queryset):
def model_class(self) -> ModelClass:
return ModelClass(self.inputs["dataset_type"])

@property
def model_class_label(self) -> str:
return constants.model_types[self.inputs["dataset_type"]]

def get_session(self, index: int) -> Session:
if not self.is_finished or self.has_errors:
raise ValueError("Session cannot be returned")
Expand Down Expand Up @@ -313,7 +320,7 @@ def execute(self):
# get prepare complete output object
analysis_output = AnalysisOutput(
analysis_id=str(self.id),
bmds_ui_version=settings.COMMIT.sha,
bmds_ui_version=__version__,
bmds_python_version=bmds_python_version,
outputs=[output.model_dump(by_alias=True) for output in outputs],
)
Expand Down Expand Up @@ -341,7 +348,6 @@ def handle_execution_error(self, err):

def default_input(self) -> dict:
return {
"bmds_version": BmdsVersion.latest(),
"dataset_type": ModelClass.CONTINUOUS,
"datasets": [],
"models": {},
Expand All @@ -354,9 +360,12 @@ def renew(self):
self.deletion_date = get_deletion_date(self.deletion_date)

def get_bmds_version(self) -> VersionSchema | None:
if not self.is_finished or self.has_errors:
if not self.is_finished or self.has_errors or self.outputs is None:
return None
try:
return VersionSchema.model_validate(self.outputs["bmds_python_version"])
except pydantic.ValidationError:
return None
return AnalysisOutput.model_validate(self.outputs).bmds_python_version

@property
def deletion_date_str(self) -> str | None:
Expand All @@ -378,6 +387,9 @@ def timestamp(self):
@reversion.register()
class Collection(models.Model):
name = models.CharField(max_length=128)
bg_color = models.CharField(
max_length=7, default="#17A2B8", validators=[RegexValidator(regex=re_hex_color)]
)
description = models.TextField(blank=True)
created = models.DateTimeField(auto_now_add=True)
last_updated = models.DateTimeField(auto_now=True)
Expand Down
2 changes: 1 addition & 1 deletion bmds_ui/analysis/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
class CollectionSerializer(serializers.ModelSerializer):
class Meta:
model = models.Collection
fields = ("id", "name")
fields = ("id", "bg_color", "name")


class AnalysisSerializer(serializers.ModelSerializer):
Expand Down
56 changes: 42 additions & 14 deletions bmds_ui/analysis/templates/analysis/desktop_home.html
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,22 @@ <h3 class="flex-grow-1">Analyses</h3>
<div class="dropdown mr-2">
<button class="btn btn-secondary dropdown-toggle" type="button" data-toggle="dropdown" aria-expanded="false">Collections</button>
<div class="dropdown-menu dropdown-menu-right">
<form class="p-2">
<a class="btn btn-primary float-right mr-4"
href="#"
hx-get="{% url 'actions' action='collection_create' %}"
hx-target="#collection-list"
hx-swap="afterbegin"><span class="bi bi-plus-square-fill mr-1"></span>Create</a>
<div class="clearfix"></div>
{% include "analysis/fragments/collection_list.html" with object_list=collection_qs no_oob=True %}
<form class="px-2" style="width: 500px">
<div class="d-flex px-3 pt-3">
<h3 class='mb-0'>Collections</h3>
<a class="btn btn-primary ml-auto align-self-start"
href="#"
hx-get="{% url 'actions' action='collection_create' %}"
hx-target="#collection-list"
hx-swap="afterbegin"><span class="bi bi-plus-square-fill mr-1"></span>Create</a>
</div>
<p class='text-muted mb-2 px-3'>Create new collections or update existing collections. Analyses can be applied to one or more collections when editing an analysis. Please refresh this page after making edits to see the changes reflected.</p>
<ul class="list-group mb-0" id="collection-list">
{% for object in collection_qs %}
{% include 'analysis/fragments/collection_li.html' %}
{% endfor %}
<li class="list-group-item show-only-child text-muted">No collections. Would you like to create one?</li>
</ul>
</form>
</div>
</div>
Expand All @@ -31,8 +39,8 @@ <h3 class="flex-grow-1">Analyses</h3>

</div>

<form class="form-row align-items-center bg-light py-3" hx-get="{% url 'home' %}" hx-sync="this:replace" hx-select="#analysis-list" hx-target="#analysis-list" hx-swap="outerHTML" hx-trigger="change,keyup from:.typeahead changed delay:0.5s">
<div class="col-auto">
<form class="shadow-sm form-row align-items-center bg-lightblue p-3" hx-get="{% url 'home' %}" hx-sync="this:replace" hx-select="#analysis-list" hx-target="#analysis-list" hx-swap="outerHTML" hx-trigger="change,keyup from:.typeahead changed delay:0.5s">
<div class="col">
<input class="form-control typeahead" name="q" value="{{q}}" type="text" placeholder="Search"/>
</div>
<div class="col-auto">
Expand All @@ -43,6 +51,14 @@ <h3 class="flex-grow-1">Analyses</h3>
{% endfor %}
</select>
</div>
<div class="col-auto">
<select class="form-control custom-select" name="modeltype">
<option value="">--- Model Type ---</option>
{% for k,v in model_types.items %}
<option value="{{k}}">{{v}}</option>
{% endfor %}
</select>
</div>
<div class="col-auto">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="starred" {{starred|yesno:"checked,,"}} name="starred">
Expand All @@ -58,24 +74,36 @@ <h3 class="flex-grow-1">Analyses</h3>
<colgroup>
<col width="40px" />
<col />
<col width="150px" />
<col width="100px" />
<col width="100px" />
</colgroup>
<thead>
<th colspan="2">Analysis</th>
<th>Type</th>
<th>Desktop Version</th>
<th>Last Updated</th>
</thead>
<tbody>
{% for object in objects %}
<tr>
{% include 'analysis/fragments/td_star.html' with object=object %}
<td class="stretched-link-parent">
{% for collection in object.collections.all %}<span class="mr-1 badge badge-info">{{collection}}</span>{% endfor %}
{% for collection in object.collections.all %}<span class="analysis-label" style="border: 3px solid {{collection.bg_color}}; background: {{collection.bg_color}}1F" >{{collection}}</span>{% endfor %}
<a class="text-dark stretched-link" href="{{object.get_edit_url}}">{{object}}</a>
</td>
<td>{{object.model_class_label}}</td>
<td>{{object.outputs.bmds_ui_version|default:"-"}}</td>
<td>{% table_time now object.timestamp %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% include "includes/pagination.html" %}
{% else %}
<p class="text-muted">No analyses found.</p>
{% elif has_query %}
<div class="px-3 py-4 text-muted">
<p class="h4">No analyses available.</p>
</div>
{% endif %}
{% endwith %}
</div>
Expand All @@ -89,7 +117,7 @@ <h4 class="mb-0 text-white text-center">Support</h4>
<ul class="mb-0">
<li><a href="https://www.epa.gov/bmds" target="_blank" rel="noopener noreferrer">BMDS Home Page</a></li>
<li><a href="https://www.epa.gov/bmds/how-modeling-dichotomous-data-bmds-online" target="_blank" rel="noopener noreferrer">BMDS Online Tutorial</a></li>
<li><a href="https://www.epa.gov/bmds/benchmark-dose-software-bmds-version-32-download" target="_blank" rel="noopener noreferrer">BMDS Downloads</a></li>
<li><a href="https://www.epa.gov/bmds/download-bmds" target="_blank" rel="noopener noreferrer">BMDS Downloads</a></li>
<li><a href="https://ecomments.epa.gov/bmds/" target="_blank" rel="noopener noreferrer">BMDS Support</a></li>
</ul>
</div>
Expand Down
59 changes: 36 additions & 23 deletions bmds_ui/analysis/templates/analysis/fragments/collection_form.html
Original file line number Diff line number Diff line change
@@ -1,23 +1,36 @@
<form class="form-inline" hx-target="closest li" hx-swap="outerHTML">
{% csrf_token %}
<div class="form-group">{{ form.name }}</div>
<div class="form-group d-flex">
{% if object.id %}
<a class="btn btn-primary" type="submit"
hx-target="#collection-list"
hx-post="{% url 'actions' action='collection_update' %}?id={{object.id}}">Update</a>
<a class="btn btn-light" href="#"
hx-get="{% url 'actions' action='collection_detail' %}?id={{object.id}}">Cancel</a>
<a class="btn btn-danger" href="#"
hx-confirm="Are you sure you wish to delete?"
hx-delete="{% url 'actions' action='collection_delete' %}?id={{object.id}}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>Delete</a>
{% else %}
<button class="btn btn-primary" type="submit"
hx-target="#collection-list"
hx-post="{% url 'actions' action='collection_create' %}">Create</button>
<button class="btn btn-light" type='button'
onclick="$(this).closest('form').remove()">Cancel</button>
{% endif %}
</div>
</form>
<li class="list-group-item bg-lightblue" hx-target="this" hx-swap="outerHTML">
<form>
{% csrf_token %}
<div class="form-group row">
<label for="name" class="col-sm-3 col-form-label">Name</label>
<div class="col-sm-9">
{{ form.name }}
{{ form.name.errors }}
</div>
</div>
<div class="form-group row">
<label for="bg_color" class="col-sm-3 col-form-label">Color</label>
<div class="col-sm-9">
{{ form.bg_color }}
{{form.bg_color.errors}}
</div>
</div>
<div class="form-group row d-flex justify-content-center">
{% if object.id %}
<button class="btn btn-primary" type="submit"
hx-post="{% url 'actions' action='collection_update' %}?id={{object.id}}">Update</button>
<a class="btn btn-light mx-4" href="#"
hx-get="{% url 'actions' action='collection_detail' %}?id={{object.id}}">Cancel</a>
<a class="btn btn-danger" href="#"
hx-confirm="Are you sure you wish to delete?"
hx-delete="{% url 'actions' action='collection_delete' %}?id={{object.id}}"
hx-headers='{"X-CSRFToken": "{{ csrf_token }}"}'>Delete</a>
{% else %}
<button class="btn btn-primary mr-3" type="submit"
hx-post="{% url 'actions' action='collection_create' %}">Create</button>
<button class="btn btn-light" type="button"
onclick="$(this).closest('.list-group-item').remove(); event.stopPropagation();">Cancel</button>
{% endif %}
</div>
</form>
</li>
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
<li class="list-group-item" hx-swap="innerHTML">{{object.name}}
<li class="list-group-item" hx-swap="outerHTML">
<span style="background: {{object.bg_color}}1F; border: 3px solid {{object.bg_color}};" >&emsp;</span>
<span class="ml-2">{{object.name}}</span>
<a class="btn btn-primary float-right" href="#" hx-get="{% url 'actions' action='collection_update' %}?id={{object.id}}" hx-target='closest li'><span class="bi bi-pencil-square mr-1"></span>Update</a>
</li>
14 changes: 0 additions & 14 deletions bmds_ui/analysis/templates/analysis/fragments/collection_list.html

This file was deleted.

4 changes: 4 additions & 0 deletions bmds_ui/analysis/utils.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from textwrap import dedent

from django.conf import settings
Expand All @@ -20,3 +21,6 @@ def get_citation() -> str:
f"""\
United States Environmental Protection Agency. ({year}). BMDS Online ({__version__}; pybmds {version.python}; bmdscore {version.dll}) [Web App]. Available from {uri}. Accessed {accessed}."""
)


re_hex_color = re.compile("^#(?:[0-9a-fA-F]{3}){1,2}$")
14 changes: 1 addition & 13 deletions bmds_ui/analysis/validators/session.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
from enum import StrEnum
from typing import Any, Self
from typing import Any

from django.conf import settings
from pydantic import BaseModel, Field
Expand All @@ -9,19 +8,8 @@
from ...common.validation import pydantic_validate


class BmdsVersion(StrEnum):
BMDS330: str = "BMDS330"
BMDS232: str = "23.2"
BMDS24: str = "24.1a"

@classmethod
def latest(cls) -> Self:
return list(cls)[-1]


class BaseSession(BaseModel):
id: int | str | None = None
bmds_version: BmdsVersion
description: str = ""
dataset_type: pybmds.constants.ModelClass

Expand Down
Loading

0 comments on commit 48776dd

Please sign in to comment.