Did a makeover for the CSS for the tooltip-- cleaner text, fade-in, rounded corners, and shadow.
Added some Javascript to make the tooltip responsive.
Updated the CSS in a few other spots.
Preview:
Preview of tooltip makeover + its responsiveness. When I hover over it, it doesn't trail off the screen.
Jinja2 updates
Added query param MD5 hashes to the CSS files. Long story short, this makes it so we can update the CSS files without worry.
Added new _flags_widget.html that gets included into other templates. This widget also has its own CSS, titled flags.css.
Renamed base.html to _base.html to indicate that it is a "private" template, in the sense that it's not intended to be rendered on its own.
Data stuff
Most notably, the Boathouses model is now named Boathouse and is in its own file called boathouses.py. (Renamed because it's far more common in ORM frameworks to use the singular, rather than plural, because when the class is initialized it represents a single row in the table.)
Using classmethod's for the Boathouse stuff that makes use of SQLAlchemy.
I'm honestly still a bit torn on the whole ORM thing for our purposes. In a professional large enterprise setting it's 100% the right thing to do. And it makes the code a lot prettier when you write it properly. For us, the case is less convincing notwithstanding the huge upside of Flask-Admin. I still think for frontend stuff we should totally use it though for general purpose things.
The way I think of it is like this as a TLDR:
Backend processing should be Pandas because we expect the CRWA to want to touch down the line. And it starts as raw data, anyway. So from both angles it makes sense to be Pandas.
The stuff that goes directly to the frontend, we don't really expect the CRWA to touch once its done. So it can be SQLAlchemy.
More detailed thoughts:
I think it's really good to add a docstring of the SQL query that's being run within each classmethod. Between the code and the docstring, I think these are super legible. Even though I don't expect a future CRWA volunteer to know SQL, I think SQL is so much easier to read than a bunch of for-loops.
That said, if the SQL is easier to read than SQLA ORM stuff, then it begs the question of why not just do everything with db.engine.connect().execute(). In a scalable production environment, there are a lot of good reasons for that: inheritance, mapping, portability, and all the reasons why people use actual classes instead of tuples more generally. I'll admit the arguments are less convincing for us.
Overall, I think the code ends up much more readable but I'm worried that, paradoxically, it becomes a little harder to edit at the same time. I actually think here readability is better precisely because I don't expect this to ever need to be changed once it's done.
The one issue I am struggling with is figuring out how to connect the Boathouse class and the predictive model outputs in a sane way, since the latter doesn't have a model for it. Still working on making it sane, but this is what I have so far:
def get_flags(df: pd.DataFrame) -> Dict[str, bool]:
"""
Get a dict of boolean values indicating whether each boathouse is considered
safe. True means it is considered safe-- otherwise it is false.
Args:
df: (pd.DataFrame) Pandas Dataframe containing predictive model outputs.
Returns:
Dict of booleans.
"""
# sql equivalent: SELECT * FROM boathouses ORDER BY boathouse
all_boathouses = (
Boathouse.query
.order_by(Boathouse.boathouse)
.all()
)
def _reach_is_safe(r: int) -> bool:
return df.loc[df['reach'] == r, 'safe'].iloc[0]
flag_statuses = {}
for row in all_boathouses:
# Check to see if the reach is safe AND the row has not been overridden.
# If both are true, then the boathouse gets a blue flag.
# Otherwise, give it a red flag.
flag_statuses[row.boathouse] = \
_reach_is_safe(row.reach) and (not row.overridden)
return flag_statuses
Other stuff
Added pip-compile wrapper in our flask app. Run with flask pip-compile and it will process all the environments.
I'm beginning to clean up some of the data processing in flagging.py and api.py. There is a lot of stuff that doesn't need to be there and a lot of things we can do to make it cleaner.
Model outputs page overhaul
(i)
icon.Preview:
Preview of tooltip makeover + its responsiveness. When I hover over it, it doesn't trail off the screen.
Jinja2 updates
_flags_widget.html
that gets included into other templates. This widget also has its own CSS, titledflags.css
.base.html
to_base.html
to indicate that it is a "private" template, in the sense that it's not intended to be rendered on its own.Data stuff
Boathouses
model is now namedBoathouse
and is in its own file calledboathouses.py
. (Renamed because it's far more common in ORM frameworks to use the singular, rather than plural, because when the class is initialized it represents a single row in the table.)classmethod
's for the Boathouse stuff that makes use of SQLAlchemy.I'm honestly still a bit torn on the whole ORM thing for our purposes. In a professional large enterprise setting it's 100% the right thing to do. And it makes the code a lot prettier when you write it properly. For us, the case is less convincing notwithstanding the huge upside of Flask-Admin. I still think for frontend stuff we should totally use it though for general purpose things.
The way I think of it is like this as a TLDR:
More detailed thoughts:
db.engine.connect().execute()
. In a scalable production environment, there are a lot of good reasons for that: inheritance, mapping, portability, and all the reasons why people use actual classes instead of tuples more generally. I'll admit the arguments are less convincing for us.The one issue I am struggling with is figuring out how to connect the Boathouse class and the predictive model outputs in a sane way, since the latter doesn't have a model for it. Still working on making it sane, but this is what I have so far:
Other stuff
pip-compile
wrapper in our flask app. Run withflask pip-compile
and it will process all the environments.flagging.py
andapi.py
. There is a lot of stuff that doesn't need to be there and a lot of things we can do to make it cleaner.