diff --git a/.gitignore b/.gitignore index 92ee412..ceabbab 100644 --- a/.gitignore +++ b/.gitignore @@ -128,4 +128,4 @@ dmypy.json .pyre/ # IDE related ignores -.vscode \ No newline at end of file +.vscode diff --git a/Dockerfile b/Dockerfile index 506eb16..ba3d108 100644 --- a/Dockerfile +++ b/Dockerfile @@ -37,4 +37,4 @@ USER wagtail EXPOSE 5000 # Run the server -CMD set -xe; gunicorn --worker-tmp-dir /dev/shm core.wsgi:application --bind 0.0.0.0:5000 --workers 3 \ No newline at end of file +CMD set -xe; gunicorn --worker-tmp-dir /dev/shm core.wsgi:application --bind 0.0.0.0:5000 --workers 3 diff --git a/Procfile b/Procfile index fce68df..3820060 100644 --- a/Procfile +++ b/Procfile @@ -1 +1 @@ -release: python manage.py migrate \ No newline at end of file +release: python manage.py migrate diff --git a/README.md b/README.md index 40ce038..a12a270 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GeriLife Caregiving -A toolkit for caregivers working to promote wellness in elder-care communities. +A toolkit for caregivers working to promote wellness in elder-care communities. ## License diff --git a/poetry.lock b/poetry.lock index d0c73f2..abdccbd 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand. [[package]] name = "asgiref" diff --git a/project/accounts/admin.py b/project/accounts/admin.py index 64b654e..f7b6a8e 100644 --- a/project/accounts/admin.py +++ b/project/accounts/admin.py @@ -4,9 +4,10 @@ from .forms import CustomUserChangeForm, CustomUserCreationForm from .models import User + @admin.register(User) class CustomUserAdmin(UserAdmin): add_form = CustomUserCreationForm form = CustomUserChangeForm model = User - list_display = ['email', 'username',] + list_display = ["email", "username"] diff --git a/project/accounts/apps.py b/project/accounts/apps.py index 3e3c765..0cb51e6 100644 --- a/project/accounts/apps.py +++ b/project/accounts/apps.py @@ -2,5 +2,5 @@ class AccountsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'accounts' + default_auto_field = "django.db.models.BigAutoField" + name = "accounts" diff --git a/project/accounts/forms.py b/project/accounts/forms.py index 5c9aa8a..0880c9c 100644 --- a/project/accounts/forms.py +++ b/project/accounts/forms.py @@ -1,18 +1,15 @@ -from django import forms from django.contrib.auth.forms import UserCreationForm, UserChangeForm from .models import User class CustomUserCreationForm(UserCreationForm): - class Meta: model = User - fields = ('username', 'email',) + fields = ("username", "email") class CustomUserChangeForm(UserChangeForm): - class Meta: model = User - fields = ('username', 'email',) + fields = ("username", "email") diff --git a/project/accounts/models.py b/project/accounts/models.py index abdf920..b756e6d 100644 --- a/project/accounts/models.py +++ b/project/accounts/models.py @@ -1,14 +1,12 @@ from django.contrib.auth.models import AbstractUser -from django.db import models from django.utils.translation import gettext_lazy as _ class User(AbstractUser): - class Meta: db_table = "user" verbose_name = _("user") verbose_name_plural = _("users") - + def __str__(self): - return self.username \ No newline at end of file + return self.username diff --git a/project/accounts/templates/registration/login.html b/project/accounts/templates/registration/login.html index 2d79cc8..7ea053d 100644 --- a/project/accounts/templates/registration/login.html +++ b/project/accounts/templates/registration/login.html @@ -6,11 +6,11 @@ {% block title %}Log in{% endblock title %} {% block content %} -

{% translate "Log in" %}

+

{% translate "Log in" %}

-
- {% csrf_token %} - {{ form | crispy }} - -
-{% endblock content %} \ No newline at end of file +
+ {% csrf_token %} + {{ form | crispy }} + +
+{% endblock content %} diff --git a/project/accounts/templates/registration/signup.html b/project/accounts/templates/registration/signup.html index eb3bf6e..02bd95c 100644 --- a/project/accounts/templates/registration/signup.html +++ b/project/accounts/templates/registration/signup.html @@ -6,11 +6,11 @@ {% block title %}{% translate "Sign up" %}{% endblock title %} {% block content %} -

{% translate "Sign up" %}

+

{% translate "Sign up" %}

-
- {% csrf_token %} - {{ form | crispy }} - -
-{% endblock content %} \ No newline at end of file +
+ {% csrf_token %} + {{ form | crispy }} + +
+{% endblock content %} diff --git a/project/accounts/tests.py b/project/accounts/tests.py index 7ce503c..a39b155 100644 --- a/project/accounts/tests.py +++ b/project/accounts/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/project/accounts/views.py b/project/accounts/views.py index 7b2cdb6..7e10bab 100644 --- a/project/accounts/views.py +++ b/project/accounts/views.py @@ -3,6 +3,7 @@ from .forms import CustomUserCreationForm + class SignUpView(CreateView): form_class = CustomUserCreationForm success_url = reverse_lazy("home") diff --git a/project/activities/forms.py b/project/activities/forms.py index c4544c1..5ed337a 100644 --- a/project/activities/forms.py +++ b/project/activities/forms.py @@ -9,4 +9,4 @@ class Meta: fields = "__all__" widgets = { "date": forms.DateInput(attrs={"type": "date"}), - } \ No newline at end of file + } diff --git a/project/activities/models.py b/project/activities/models.py index 518d03a..4b118d8 100644 --- a/project/activities/models.py +++ b/project/activities/models.py @@ -3,13 +3,11 @@ class Activity(models.Model): - """ - Model representing an activity. - """ + """Model representing an activity.""" + class ActivityTypeChoices(models.TextChoices): - """ - Choices for the type of activity. - """ + """Choices for the type of activity.""" + OUTDOOR = "outdoor", _("Outdoor") CULTURE = "culture", _("Culture") DISCUSSION = "discussion", _("Discussion") @@ -34,7 +32,7 @@ class ActivityTypeChoices(models.TextChoices): _("Duration in minutes"), default=30, ) - + class Meta: db_table = "activity" verbose_name = _("activity") diff --git a/project/activities/templates/activities/form.html b/project/activities/templates/activities/form.html index 083728e..dd04c9a 100644 --- a/project/activities/templates/activities/form.html +++ b/project/activities/templates/activities/form.html @@ -5,11 +5,11 @@ {% block title %}Add activity{% endblock title %} {% block content %} -
- {% csrf_token %} + + {% csrf_token %} - {{ form|crispy }} + {{ form|crispy }} - -
-{% endblock content %} \ No newline at end of file + + +{% endblock content %} diff --git a/project/activities/templates/activities/list.html b/project/activities/templates/activities/list.html index af464ad..38f9c26 100644 --- a/project/activities/templates/activities/list.html +++ b/project/activities/templates/activities/list.html @@ -25,5 +25,6 @@

Activities

{{ activity.duration_minutes }} {% endfor %} + {% endblock %} diff --git a/project/activities/tests.py b/project/activities/tests.py index 7ce503c..a39b155 100644 --- a/project/activities/tests.py +++ b/project/activities/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/project/activities/urls.py b/project/activities/urls.py index 3ed73bb..2df4d5b 100644 --- a/project/activities/urls.py +++ b/project/activities/urls.py @@ -1,11 +1,9 @@ from django.urls import path -from django.utils.translation import gettext_lazy as _ from .views import ActivityFormView, ActivityListView urlpatterns = [ - path( # . Translators: Make sure to leave the trailing slash "/" "submit/", diff --git a/project/caregivers/admin.py b/project/caregivers/admin.py index 3e91fb1..118a4da 100644 --- a/project/caregivers/admin.py +++ b/project/caregivers/admin.py @@ -2,6 +2,7 @@ from .models import CaregiverRole + @admin.register(CaregiverRole) class CaregiverRoleAdmin(admin.ModelAdmin): pass diff --git a/project/caregivers/apps.py b/project/caregivers/apps.py index 73e39ad..6580e7a 100644 --- a/project/caregivers/apps.py +++ b/project/caregivers/apps.py @@ -2,5 +2,5 @@ class CaregiversConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'caregivers' + default_auto_field = "django.db.models.BigAutoField" + name = "caregivers" diff --git a/project/caregivers/tests.py b/project/caregivers/tests.py index 7ce503c..a39b155 100644 --- a/project/caregivers/tests.py +++ b/project/caregivers/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/project/caregivers/views.py b/project/caregivers/views.py index 91ea44a..60f00ef 100644 --- a/project/caregivers/views.py +++ b/project/caregivers/views.py @@ -1,3 +1 @@ -from django.shortcuts import render - # Create your views here. diff --git a/project/core/asgi.py b/project/core/asgi.py index 04e6d00..7e26747 100644 --- a/project/core/asgi.py +++ b/project/core/asgi.py @@ -1,5 +1,4 @@ -""" -ASGI config for core project. +"""ASGI config for core project. It exposes the ASGI callable as a module-level variable named ``application``. @@ -11,6 +10,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") application = get_asgi_application() diff --git a/project/core/settings.py b/project/core/settings.py index 4ab8312..894d1d4 100644 --- a/project/core/settings.py +++ b/project/core/settings.py @@ -1,5 +1,4 @@ -""" -Django settings for core project. +"""Django settings for core project. Generated by 'django-admin startproject' using Django 4.0. @@ -152,7 +151,14 @@ STATIC_URL = "static/" STATIC_ROOT = BASE_DIR / "static" -STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" +STORAGES = { + "default": { + "BACKEND": "django.core.files.storage.FileSystemStorage", + }, + "staticfiles": { + "BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage", + }, +} # Default primary key field type # https://docs.djangoproject.com/en/4.0/ref/settings/#default-auto-field diff --git a/project/core/urls.py b/project/core/urls.py index cad4ca7..7324fd3 100644 --- a/project/core/urls.py +++ b/project/core/urls.py @@ -1,4 +1,4 @@ -"""core URL Configuration +"""Core URL Configuration. The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/4.0/topics/http/urls/ @@ -15,7 +15,6 @@ """ from django.contrib import admin from django.urls import include, path -from django.utils.translation import gettext_lazy as _ from django.views.generic.base import TemplateView urlpatterns = [ diff --git a/project/core/wsgi.py b/project/core/wsgi.py index 08cbd4c..3a3f11a 100644 --- a/project/core/wsgi.py +++ b/project/core/wsgi.py @@ -1,5 +1,4 @@ -""" -WSGI config for core project. +"""WSGI config for core project. It exposes the WSGI callable as a module-level variable named ``application``. @@ -11,6 +10,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") application = get_wsgi_application() diff --git a/project/homes/admin.py b/project/homes/admin.py index 99f9dbb..1e7d9e7 100644 --- a/project/homes/admin.py +++ b/project/homes/admin.py @@ -2,6 +2,7 @@ from .models import Home + @admin.register(Home) class HomeAdmin(admin.ModelAdmin): - pass \ No newline at end of file + pass diff --git a/project/homes/apps.py b/project/homes/apps.py index 10d5e34..4a5e0e9 100644 --- a/project/homes/apps.py +++ b/project/homes/apps.py @@ -2,5 +2,5 @@ class HomesConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'homes' + default_auto_field = "django.db.models.BigAutoField" + name = "homes" diff --git a/project/homes/charts.py b/project/homes/charts.py index 57e8a6c..27b8820 100644 --- a/project/homes/charts.py +++ b/project/homes/charts.py @@ -6,7 +6,8 @@ def dictfetchall(cursor): - """Return a list of dictionaries containing all rows from a database cursor""" + """Return a list of dictionaries containing all rows from a database + cursor.""" columns = [col[0] for col in cursor.description] return [dict(zip(columns, row)) for row in cursor.fetchall()] @@ -17,7 +18,7 @@ def get_daily_total_hours_by_role_and_work_type_with_percent(home_id): select date, caregiver_role.name as role_name, - work_type.name as work_type, + work_type.name as work_type, sum(duration_hours) as daily_total_hours from work left join work_type on type_id = work_type.id @@ -26,13 +27,13 @@ def get_daily_total_hours_by_role_and_work_type_with_percent(home_id): group by date, role_name, work_type ), daily_work_totals_by_type_with_role_total_hours as ( - select + select *, sum(daily_total_hours) over (partition by date, role_name) as daily_role_total_hours from daily_work_totals_by_type ) - select + select *, CAST(daily_total_hours as float) / CAST(daily_role_total_hours as float) as percent_of_daily_role_total_hours from daily_work_totals_by_type_with_role_total_hours; @@ -49,9 +50,9 @@ def get_daily_total_hours_by_role_and_work_type_with_percent(home_id): def get_total_hours_by_role_and_work_type_with_percent(home_id): query = """ with work_totals_by_type as ( - select + select caregiver_role.name as role_name, - work_type.name as work_type, + work_type.name as work_type, sum(duration_hours) as total_hours from work left join work_type on type_id = work_type.id @@ -60,13 +61,13 @@ def get_total_hours_by_role_and_work_type_with_percent(home_id): group by role_name, work_type ), work_totals_by_type_with_role_total_hours as ( - select + select *, sum(total_hours) over (partition by role_name) as role_total_hours from work_totals_by_type ) - select + select *, CAST(total_hours as float) / CAST(role_total_hours as float) as percent_of_role_total_hours from work_totals_by_type_with_role_total_hours; @@ -107,11 +108,12 @@ def get_home_total_hours_by_role_with_percent(home_id): return result + def prepare_work_by_type_chart(home): work_by_type = list( home.work_performed.values("type__name") .order_by("type__name") - .annotate(total_hours=Sum("duration_hours")) + .annotate(total_hours=Sum("duration_hours")), ) work_by_type_chart = px.bar( @@ -127,11 +129,12 @@ def prepare_work_by_type_chart(home): return work_by_type_chart + def prepare_work_by_caregiver_role_chart(home): work_by_caregiver_role = list( home.work_performed.values("caregiver_role__name") .order_by("caregiver_role__name") - .annotate(total_hours=Sum("duration_hours")) + .annotate(total_hours=Sum("duration_hours")), ) work_by_caregiver_role_chart = px.bar( @@ -147,8 +150,11 @@ def prepare_work_by_caregiver_role_chart(home): return work_by_caregiver_role_chart + def prepare_daily_work_percent_by_caregiver_role_and_type_chart(home): - daily_total_hours_by_role_and_work_type_with_percent = get_daily_total_hours_by_role_and_work_type_with_percent(home.id) + daily_total_hours_by_role_and_work_type_with_percent = ( + get_daily_total_hours_by_role_and_work_type_with_percent(home.id) + ) daily_work_percent_by_caregiver_role_and_type_chart = px.bar( daily_total_hours_by_role_and_work_type_with_percent, @@ -167,20 +173,25 @@ def prepare_daily_work_percent_by_caregiver_role_and_type_chart(home): ) # Format y-axis as percentages - daily_work_percent_by_caregiver_role_and_type_chart.update_yaxes(tickformat = ",.0%") - + daily_work_percent_by_caregiver_role_and_type_chart.update_yaxes(tickformat=",.0%") + # Remove facet prefix from facet row labels - daily_work_percent_by_caregiver_role_and_type_chart.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1])) - + daily_work_percent_by_caregiver_role_and_type_chart.for_each_annotation( + lambda a: a.update(text=a.text.split("=")[-1]), + ) + # Ensure that all bar widths are one day (where units are in milliseconds) one_day = 24 * 60 * 60 * 1000 daily_work_percent_by_caregiver_role_and_type_chart.update_traces(width=one_day) return daily_work_percent_by_caregiver_role_and_type_chart.to_html() + def prepare_home_work_percent_by_caregiver_role_chart(home): - home_work_percent_by_caregiver_role = get_home_total_hours_by_role_with_percent(home.id) - + home_work_percent_by_caregiver_role = get_home_total_hours_by_role_with_percent( + home.id, + ) + home_work_percent_by_caregiver_role_chart = px.bar( home_work_percent_by_caregiver_role, color="role_name", @@ -203,7 +214,7 @@ def prepare_home_work_percent_by_caregiver_role_chart(home): "t": 0, "pad": 0, }, - plot_bgcolor= "rgba(0, 0, 0, 0)", + plot_bgcolor="rgba(0, 0, 0, 0)", showlegend=False, xaxis={ "tickformat": ",.0%", @@ -212,7 +223,7 @@ def prepare_home_work_percent_by_caregiver_role_chart(home): "visible": False, }, ) - + return home_work_percent_by_caregiver_role_chart.to_html( config={ "displayModeBar": False, @@ -220,7 +231,9 @@ def prepare_home_work_percent_by_caregiver_role_chart(home): ) -def prepare_work_percent_by_caregiver_role_and_type_chart(work_by_caregiver_role_and_type_with_percent): +def prepare_work_percent_by_caregiver_role_and_type_chart( + work_by_caregiver_role_and_type_with_percent, +): work_percent_by_caregiver_role_and_type_chart = px.bar( work_by_caregiver_role_and_type_with_percent, x="role_name", @@ -238,7 +251,10 @@ def prepare_work_percent_by_caregiver_role_and_type_chart(work_by_caregiver_role return work_percent_by_caregiver_role_and_type_chart.to_html() -def prepare_work_by_caregiver_role_and_type_chart(work_by_caregiver_role_and_type_with_percent): + +def prepare_work_by_caregiver_role_and_type_chart( + work_by_caregiver_role_and_type_with_percent, +): work_by_caregiver_role_and_type_chart = px.bar( work_by_caregiver_role_and_type_with_percent, x="role_name", @@ -250,7 +266,6 @@ def prepare_work_by_caregiver_role_and_type_chart(work_by_caregiver_role_and_typ "total_hours": _("Total hours"), "work_type": _("Type of work"), }, - ) return work_by_caregiver_role_and_type_chart.to_html() @@ -259,10 +274,20 @@ def prepare_work_by_caregiver_role_and_type_chart(work_by_caregiver_role_and_typ def prepare_work_by_caregiver_role_and_type_charts(context): home = context["home"] - work_by_caregiver_role_and_type_with_percent = get_total_hours_by_role_and_work_type_with_percent(home.id) + work_by_caregiver_role_and_type_with_percent = ( + get_total_hours_by_role_and_work_type_with_percent(home.id) + ) - context["work_percent_by_caregiver_role_and_type_chart"] = prepare_work_percent_by_caregiver_role_and_type_chart(work_by_caregiver_role_and_type_with_percent) + context[ + "work_percent_by_caregiver_role_and_type_chart" + ] = prepare_work_percent_by_caregiver_role_and_type_chart( + work_by_caregiver_role_and_type_with_percent, + ) - context["work_by_caregiver_role_and_type_chart"] = prepare_work_by_caregiver_role_and_type_chart(work_by_caregiver_role_and_type_with_percent) + context[ + "work_by_caregiver_role_and_type_chart" + ] = prepare_work_by_caregiver_role_and_type_chart( + work_by_caregiver_role_and_type_with_percent, + ) return context diff --git a/project/homes/models.py b/project/homes/models.py index 65fe575..6fb6f5d 100644 --- a/project/homes/models.py +++ b/project/homes/models.py @@ -15,4 +15,4 @@ def __str__(self) -> str: return self.name def get_absolute_url(self): - return reverse("home-detail-view", kwargs = {"pk": self.id}) + return reverse("home-detail-view", kwargs={"pk": self.id}) diff --git a/project/homes/templates/homes/home_detail.html b/project/homes/templates/homes/home_detail.html index 379d01e..d897cec 100644 --- a/project/homes/templates/homes/home_detail.html +++ b/project/homes/templates/homes/home_detail.html @@ -2,7 +2,7 @@ {% load i18n %} {% block content %} -

{{ home.name }}

+

{{ home.name }}

{% if work_has_been_recorded %} @@ -10,4 +10,4 @@

{{ home.name }}

{% else %}

{% translate "No work has been recorded yet." %}

{% endif %} -{% endblock content %} \ No newline at end of file +{% endblock content %} diff --git a/project/homes/templates/homes/home_detail_charts.html b/project/homes/templates/homes/home_detail_charts.html index 6981dab..2e02909 100644 --- a/project/homes/templates/homes/home_detail_charts.html +++ b/project/homes/templates/homes/home_detail_charts.html @@ -28,4 +28,4 @@
{{ work_by_caregiver_role_and_type_chart|safe }}
- \ No newline at end of file + diff --git a/project/homes/templates/homes/home_list.html b/project/homes/templates/homes/home_list.html index 3d3934e..7c86c00 100644 --- a/project/homes/templates/homes/home_list.html +++ b/project/homes/templates/homes/home_list.html @@ -8,11 +8,11 @@

{% translate "Homes" %}

-
+
{% translate "Home name" %}
-
+
{% translate "Work percent by role" %}
diff --git a/project/homes/templatetags/home_work_percent_by_role.py b/project/homes/templatetags/home_work_percent_by_role.py index a5016fe..ac4c59c 100644 --- a/project/homes/templatetags/home_work_percent_by_role.py +++ b/project/homes/templatetags/home_work_percent_by_role.py @@ -4,9 +4,11 @@ register = template.Library() -@register.filter(name='work_percent_by_role_chart') + +@register.filter(name="work_percent_by_role_chart") def work_percent_by_role_chart(home): - """Returns a chart showing the proportion of work carried out by each role for the given home.""" + """Returns a chart showing the proportion of work carried out by each role + for the given home.""" chart = prepare_home_work_percent_by_caregiver_role_chart(home) return chart diff --git a/project/homes/tests.py b/project/homes/tests.py index 7ce503c..a39b155 100644 --- a/project/homes/tests.py +++ b/project/homes/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/project/homes/views.py b/project/homes/views.py index 9e6cd66..adab2bc 100644 --- a/project/homes/views.py +++ b/project/homes/views.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from django.views.generic.detail import DetailView from django.views.generic.list import ListView @@ -7,11 +7,12 @@ prepare_daily_work_percent_by_caregiver_role_and_type_chart, prepare_work_by_caregiver_role_and_type_charts, prepare_work_by_caregiver_role_chart, - prepare_work_by_type_chart + prepare_work_by_type_chart, ) from .models import Home + class HomeListView(ListView): model = Home context_object_name = "homes" @@ -22,20 +23,24 @@ class HomeDetailView(DetailView): context_object_name = "home" def prepare_charts(self, context): - """Prepare charts and add them to the template context""" + """Prepare charts and add them to the template context.""" home = context["home"] context["work_by_type_chart"] = prepare_work_by_type_chart(home) - context["work_by_caregiver_role_chart"] = prepare_work_by_caregiver_role_chart(home) + context["work_by_caregiver_role_chart"] = prepare_work_by_caregiver_role_chart( + home, + ) - context["daily_work_percent_by_caregiver_role_and_type_chart"] = prepare_daily_work_percent_by_caregiver_role_and_type_chart(home) + context[ + "daily_work_percent_by_caregiver_role_and_type_chart" + ] = prepare_daily_work_percent_by_caregiver_role_and_type_chart(home) context = prepare_work_by_caregiver_role_and_type_charts(context) return context - def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) home = context["home"] diff --git a/project/locale/fi/LC_MESSAGES/django.po b/project/locale/fi/LC_MESSAGES/django.po index a7a1612..44f4361 100644 --- a/project/locale/fi/LC_MESSAGES/django.po +++ b/project/locale/fi/LC_MESSAGES/django.po @@ -293,4 +293,3 @@ msgstr "Tämän työn suorittamiseen käytetty minuuttimäärä" #: work/templates/work/report.html:7 msgid "Work report" msgstr "Työraportti" - diff --git a/project/manage.py b/project/manage.py index f2a662c..4729767 100755 --- a/project/manage.py +++ b/project/manage.py @@ -6,17 +6,17 @@ def main(): """Run administrative tasks.""" - os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'core.settings') + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "core.settings") try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " - "forget to activate a virtual environment?" + "forget to activate a virtual environment?", ) from exc execute_from_command_line(sys.argv) -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/project/residents/admin.py b/project/residents/admin.py index e0425d9..9f088ca 100644 --- a/project/residents/admin.py +++ b/project/residents/admin.py @@ -3,10 +3,12 @@ from .models import Residency, Resident + @admin.register(Resident) class ResidentAdmin(admin.ModelAdmin): pass + @admin.register(Residency) class ResidencyAdmin(admin.ModelAdmin): pass diff --git a/project/residents/apps.py b/project/residents/apps.py index 092b5fc..1d3e5c1 100644 --- a/project/residents/apps.py +++ b/project/residents/apps.py @@ -2,5 +2,5 @@ class ResidentsConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'residents' + default_auto_field = "django.db.models.BigAutoField" + name = "residents" diff --git a/project/residents/models.py b/project/residents/models.py index e200a5c..a8be199 100644 --- a/project/residents/models.py +++ b/project/residents/models.py @@ -1,4 +1,3 @@ -from datetime import datetime import uuid from django.core.exceptions import ValidationError @@ -58,8 +57,8 @@ def __str__(self) -> str: return f"{ self.resident.full_name } - { self.home.name }" def clean(self) -> None: - """ - Ensure residency is valid. + """Ensure residency is valid. + - move in date preceeds move out date, if present - resident should reside in only one home at a time (no residency overlap) """ @@ -69,7 +68,7 @@ def clean(self) -> None: # Move in date is before move out (or on same date) residency_timespan_is_valid = self.move_in <= self.move_out - # No residencies should exist for this resident + # No residencies should exist for this resident # with overlapping move in/out dates (same date is fine) residency_has_overlap = Residency.objects.filter( resident=self.resident, @@ -77,14 +76,13 @@ def clean(self) -> None: move_out__gt=self.move_in, ).exists() else: - # No residencies should exist for this resident + # No residencies should exist for this resident # with overlapping move out date (same date is fine) residency_has_overlap = Residency.objects.filter( resident=self.resident, move_out__gt=self.move_in, ).exists() - if not residency_timespan_is_valid: error_message = _("Move-in date should preceed move-out date.") raise ValidationError(error_message) diff --git a/project/residents/templates/residents/resident_detail.html b/project/residents/templates/residents/resident_detail.html index 96ca26e..8441f7a 100644 --- a/project/residents/templates/residents/resident_detail.html +++ b/project/residents/templates/residents/resident_detail.html @@ -4,14 +4,14 @@ {% block content %} -

- {{ resident.full_name }} +

+ {{ resident.full_name }} - - {% translate "Edit" %} - -

+ + {% translate "Edit" %} + + {% endblock content %} diff --git a/project/residents/templates/residents/resident_form.html b/project/residents/templates/residents/resident_form.html index c89bf98..9911c7d 100644 --- a/project/residents/templates/residents/resident_form.html +++ b/project/residents/templates/residents/resident_form.html @@ -3,17 +3,17 @@ {% load crispy_forms_tags %} {% block content %} -

- {% translate "Resident" %} -

+

+ {% translate "Resident" %} +

-
- {% csrf_token %} - - {{ form | crispy }} + + {% csrf_token %} - -
-{% endblock content %} \ No newline at end of file + {{ form | crispy }} + + + +{% endblock content %} diff --git a/project/residents/templates/residents/resident_list.html b/project/residents/templates/residents/resident_list.html index 3fe54df..d5e2d80 100644 --- a/project/residents/templates/residents/resident_list.html +++ b/project/residents/templates/residents/resident_list.html @@ -2,52 +2,52 @@ {% load i18n %} {% block content %} -

- {% translate "Residents" %} - - {% translate "Create" %} - -

+

+ {% translate "Residents" %} + + {% translate "Create" %} + +

-{% if residents %} - - - - - - - - - - {% for resident in residents %} - - - - - - {% endfor %} - -
- {% translate "View" %} - - {% translate "First name" %} - - {% translate "Last initial" %} -
- - - - {{ resident.first_name }}{{ resident.last_initial }}
-{% else %} -

{% translate "No residents found." %}

-{% endif %} -{% endblock content %} \ No newline at end of file + {% if residents %} + + + + + + + + + + {% for resident in residents %} + + + + + + {% endfor %} + +
+ {% translate "View" %} + + {% translate "First name" %} + + {% translate "Last initial" %} +
+ + + + {{ resident.first_name }}{{ resident.last_initial }}
+ {% else %} +

{% translate "No residents found." %}

+ {% endif %} +{% endblock content %} diff --git a/project/residents/tests.py b/project/residents/tests.py index b76824d..3caa284 100644 --- a/project/residents/tests.py +++ b/project/residents/tests.py @@ -1,25 +1,23 @@ from django.core.exceptions import ValidationError from django.test import TestCase -from psycopg2 import Date from .models import Residency, Resident from homes.models import Home + class ResidencyTestCase(TestCase): def setUp(self): self.resident = Resident.objects.create( first_name="Test", - last_initial="U" + last_initial="U", ) self.home = Home.objects.create( - name="Test Home" + name="Test Home", ) def test_residency_dates_are_valid(self): - """ - Creating a residency where the move out date is before move in - should raise a validation error - """ + """Creating a residency where the move out date is before move in + should raise a validation error.""" with self.assertRaises(ValidationError): residency = Residency.objects.create( resident=self.resident, @@ -37,10 +35,8 @@ def test_residency_dates_are_valid(self): residency.clean() def test_overlapping_residency_not_allowed(self): - """ - Residents should only live in one home at a time - so overlapping residencies should not be allowed - """ + """Residents should only live in one home at a time so overlapping + residencies should not be allowed.""" with self.assertRaises(ValidationError): # Create a residency that will overlap with the new residency diff --git a/project/residents/urls.py b/project/residents/urls.py index 17834ac..7fa3ff6 100644 --- a/project/residents/urls.py +++ b/project/residents/urls.py @@ -1,5 +1,4 @@ from django.urls import path -from django.utils.translation import gettext_lazy as _ from .views import ( ResidentCreateView, diff --git a/project/residents/views.py b/project/residents/views.py index f9a4077..25fe9d7 100644 --- a/project/residents/views.py +++ b/project/residents/views.py @@ -9,10 +9,10 @@ class ResidentCreateView(CreateView): model = Resident - fields = ["first_name", "last_initial", "on_hiatus",] + fields = ["first_name", "last_initial", "on_hiatus"] -class ResidentDetailView(LoginRequiredMixin,DetailView): +class ResidentDetailView(LoginRequiredMixin, DetailView): model = Resident context_object_name = "resident" template_name = "residents/resident_detail.html" @@ -20,7 +20,7 @@ class ResidentDetailView(LoginRequiredMixin,DetailView): class ResidentUpdateView(LoginRequiredMixin, UpdateView): model = Resident - fields = ["first_name", "last_initial", "on_hiatus",] + fields = ["first_name", "last_initial", "on_hiatus"] class ResidentListView(ListView): diff --git a/project/templates/base.html b/project/templates/base.html index 8216f37..9de1b6d 100644 --- a/project/templates/base.html +++ b/project/templates/base.html @@ -1,26 +1,26 @@ - - - - - {% block title %}{% endblock title %} + + + + + {% block title %}{% endblock title %} - - + + - {% block extra_css %}{% endblock extra_css %} - - - {% include "navigation.html" %} - -
- {% block content %} - {% endblock content %} -
+ {% block extra_css %}{% endblock extra_css %} + + + {% include "navigation.html" %} + +
+ {% block content %} + {% endblock content %} +
- {% block extra_js %}{% endblock extra_js %} - - - \ No newline at end of file + {% block extra_js %}{% endblock extra_js %} + + + diff --git a/project/templates/home.html b/project/templates/home.html index 0f3aefb..a2251ed 100644 --- a/project/templates/home.html +++ b/project/templates/home.html @@ -5,8 +5,8 @@ {% block title %}{% translate "Home" %}{% endblock title %} {% block content %} -

{% translate "Welcome" %}

+

{% translate "Welcome" %}

{% comment %}Translators: Leave the text "GeriLife Caregivers" as it is.{% endcomment %} -

{% translate "Welcome to GeriLife Caregivers." %}

+

{% translate "Welcome to GeriLife Caregivers." %}

{% endblock content %} diff --git a/project/templates/i18n.html b/project/templates/i18n.html index 702c34e..4ec04e2 100644 --- a/project/templates/i18n.html +++ b/project/templates/i18n.html @@ -11,15 +11,15 @@ {% get_available_languages as LANGUAGES %} {% get_language_info_list for LANGUAGES as languages %} -
\ No newline at end of file +
diff --git a/project/templates/navigation.html b/project/templates/navigation.html index a6844c1..7c63a74 100644 --- a/project/templates/navigation.html +++ b/project/templates/navigation.html @@ -43,7 +43,7 @@ - + - \ No newline at end of file + diff --git a/project/work/admin.py b/project/work/admin.py index 744be9b..3399db4 100644 --- a/project/work/admin.py +++ b/project/work/admin.py @@ -2,6 +2,7 @@ from .models import Work, WorkType + @admin.register(Work) class WorkAdmin(admin.ModelAdmin): pass @@ -9,4 +10,4 @@ class WorkAdmin(admin.ModelAdmin): @admin.register(WorkType) class WorkTypeAdmin(admin.ModelAdmin): - pass \ No newline at end of file + pass diff --git a/project/work/apps.py b/project/work/apps.py index 9d1b116..83212ce 100644 --- a/project/work/apps.py +++ b/project/work/apps.py @@ -2,5 +2,5 @@ class WorkConfig(AppConfig): - default_auto_field = 'django.db.models.BigAutoField' - name = 'work' + default_auto_field = "django.db.models.BigAutoField" + name = "work" diff --git a/project/work/context_processors.py b/project/work/context_processors.py index 60146cd..1b58a03 100644 --- a/project/work/context_processors.py +++ b/project/work/context_processors.py @@ -1,9 +1,6 @@ -from django import template - - from work.forms import WorkForm def get_work_form(request): - """Return an instance of the WorkForm""" - return {"work_form": WorkForm} \ No newline at end of file + """Return an instance of the WorkForm.""" + return {"work_form": WorkForm} diff --git a/project/work/models.py b/project/work/models.py index 9231c17..5001527 100644 --- a/project/work/models.py +++ b/project/work/models.py @@ -40,12 +40,12 @@ class Work(models.Model): ) date = models.DateField(help_text=_("The date this work was performed")) duration_minutes = models.PositiveIntegerField( - help_text=_("The number of minutes used performing this work") + help_text=_("The number of minutes used performing this work"), ) duration_hours = models.FloatField( validators=[ MinValueValidator(0.0), - ] + ], ) class Meta: @@ -54,7 +54,8 @@ class Meta: verbose_name_plural = _("work") constraints = ( CheckConstraint( - check=Q(duration_hours__gte=0.0), name="work_duration_hours_gte_zero" + check=Q(duration_hours__gte=0.0), + name="work_duration_hours_gte_zero", ), ) @@ -65,7 +66,7 @@ def get_duration_hours(self): def save(self, *args, **kwargs): self.duration_hours = self.get_duration_hours() - super(Work, self).save(*args, **kwargs) + super().save(*args, **kwargs) def __str__(self): return f"{ self.home } - { self.caregiver_role } - { self.type } - { self.date } - { self.duration }" diff --git a/project/work/templates/work/form.html b/project/work/templates/work/form.html index 1b1154a..c3e4fe1 100644 --- a/project/work/templates/work/form.html +++ b/project/work/templates/work/form.html @@ -1,11 +1,11 @@ {% extends "base.html" %} {% block content %} -
- {% csrf_token %} + + {% csrf_token %} - {{ work_form|crispy }} + {{ work_form|crispy }} - -
-{% endblock content %} \ No newline at end of file + + +{% endblock content %} diff --git a/project/work/templates/work/report.html b/project/work/templates/work/report.html index c2a865d..33e6c90 100644 --- a/project/work/templates/work/report.html +++ b/project/work/templates/work/report.html @@ -4,7 +4,7 @@ {% load static %} {% block content %} -

{% translate "Work report" %}

+

{% translate "Work report" %}

{% if work_has_been_recorded %} diff --git a/project/work/tests.py b/project/work/tests.py index 7ce503c..a39b155 100644 --- a/project/work/tests.py +++ b/project/work/tests.py @@ -1,3 +1 @@ -from django.test import TestCase - # Create your tests here. diff --git a/project/work/urls.py b/project/work/urls.py index f7bc91e..91f4067 100644 --- a/project/work/urls.py +++ b/project/work/urls.py @@ -1,5 +1,4 @@ from django.urls import path -from django.utils.translation import gettext_lazy as _ from .views import WorkFormView, WorkReportView diff --git a/project/work/views.py b/project/work/views.py index 3a6850d..7849a4a 100644 --- a/project/work/views.py +++ b/project/work/views.py @@ -1,4 +1,4 @@ -from typing import Any, Dict +from typing import Any from django.db import connection from django.db.models import Sum @@ -15,7 +15,8 @@ def dictfetchall(cursor): - """Return a list of dictionaries containing all rows from a database cursor""" + """Return a list of dictionaries containing all rows from a database + cursor.""" columns = [col[0] for col in cursor.description] return [dict(zip(columns, row)) for row in cursor.fetchall()] @@ -26,7 +27,7 @@ def get_daily_total_hours_by_role_and_work_type_with_percent(): select date, caregiver_role.name as role_name, - work_type.name as work_type, + work_type.name as work_type, sum(duration_hours) as daily_total_hours from work left join work_type on type_id = work_type.id @@ -34,13 +35,13 @@ def get_daily_total_hours_by_role_and_work_type_with_percent(): group by date, role_name, work_type ), daily_work_totals_by_type_with_role_total_hours as ( - select + select *, sum(daily_total_hours) over (partition by date, role_name) as daily_role_total_hours from daily_work_totals_by_type ) - select + select *, CAST(daily_total_hours as float) / CAST(daily_role_total_hours as float) as percent_of_daily_role_total_hours from daily_work_totals_by_type_with_role_total_hours; @@ -57,9 +58,9 @@ def get_daily_total_hours_by_role_and_work_type_with_percent(): def get_total_hours_by_role_and_work_type_with_percent(): query = """ with work_totals_by_type as ( - select + select caregiver_role.name as role_name, - work_type.name as work_type, + work_type.name as work_type, sum(duration_hours) as total_hours from work left join work_type on type_id = work_type.id @@ -67,13 +68,13 @@ def get_total_hours_by_role_and_work_type_with_percent(): group by role_name, work_type ), work_totals_by_type_with_role_total_hours as ( - select + select *, sum(total_hours) over (partition by role_name) as role_total_hours from work_totals_by_type ) - select + select *, CAST(total_hours as float) / CAST(role_total_hours as float) as percent_of_role_total_hours from work_totals_by_type_with_role_total_hours; @@ -86,16 +87,17 @@ def get_total_hours_by_role_and_work_type_with_percent(): return result + def get_work_by_type_data(): work_by_type = ( - Work.objects - .values("type__name") - .order_by("type__name") - .annotate(total_hours=Sum("duration_hours")) + Work.objects.values("type__name") + .order_by("type__name") + .annotate(total_hours=Sum("duration_hours")) ) return list(work_by_type) + def prepare_work_by_type_chart(data): work_by_type_chart = px.bar( data, @@ -113,14 +115,14 @@ def prepare_work_by_type_chart(data): def get_work_by_caregiver_role_data(): work_by_caregiver_role_data = ( - Work.objects - .values("caregiver_role__name") - .order_by("caregiver_role__name") - .annotate(total_hours=Sum("duration_hours")) + Work.objects.values("caregiver_role__name") + .order_by("caregiver_role__name") + .annotate(total_hours=Sum("duration_hours")) ) return list(work_by_caregiver_role_data) + def prepare_work_by_caregiver_role_chart(data): work_by_caregiver_role_chart = px.bar( data, @@ -154,17 +156,20 @@ def prepare_daily_work_percent_by_caregiver_role_and_type_chart(data): ) # Format y-axis as percentages - daily_work_percent_by_caregiver_role_and_type_chart.update_yaxes(tickformat = ",.0%") - + daily_work_percent_by_caregiver_role_and_type_chart.update_yaxes(tickformat=",.0%") + # Remove facet prefix from facet row labels - daily_work_percent_by_caregiver_role_and_type_chart.for_each_annotation(lambda a: a.update(text=a.text.split("=")[-1])) - + daily_work_percent_by_caregiver_role_and_type_chart.for_each_annotation( + lambda a: a.update(text=a.text.split("=")[-1]), + ) + # Ensure that all bar widths are one day (where units are in milliseconds) one_day = 24 * 60 * 60 * 1000 daily_work_percent_by_caregiver_role_and_type_chart.update_traces(width=one_day) return daily_work_percent_by_caregiver_role_and_type_chart.to_html() + def prepare_work_percent_by_caregiver_role_and_type_chart(data): work_percent_by_caregiver_role_and_type_chart = px.bar( data, @@ -183,6 +188,7 @@ def prepare_work_percent_by_caregiver_role_and_type_chart(data): return work_percent_by_caregiver_role_and_type_chart.to_html() + def prepare_work_by_caregiver_role_and_type_chart(data): work_by_caregiver_role_and_type_chart = px.bar( data, @@ -195,7 +201,6 @@ def prepare_work_by_caregiver_role_and_type_chart(data): "total_hours": _("Total hours"), "work_type": _("Type of work"), }, - ) return work_by_caregiver_role_and_type_chart.to_html() @@ -205,30 +210,38 @@ class WorkReportView(TemplateView): template_name = "work/report.html" def prepare_charts(self, context): - """Prepare data/charts and add them to the template context""" + """Prepare data/charts and add them to the template context.""" context["work_by_type_chart"] = prepare_work_by_type_chart( - get_work_by_type_data() + get_work_by_type_data(), ) context["work_by_caregiver_role_chart"] = prepare_work_by_caregiver_role_chart( - get_work_by_caregiver_role_data() + get_work_by_caregiver_role_data(), ) - context["daily_work_percent_by_caregiver_role_and_type_chart"] = prepare_daily_work_percent_by_caregiver_role_and_type_chart( - get_daily_total_hours_by_role_and_work_type_with_percent() + context[ + "daily_work_percent_by_caregiver_role_and_type_chart" + ] = prepare_daily_work_percent_by_caregiver_role_and_type_chart( + get_daily_total_hours_by_role_and_work_type_with_percent(), ) - work_by_caregiver_role_and_type_with_percent = get_total_hours_by_role_and_work_type_with_percent() - context["work_percent_by_caregiver_role_and_type_chart"] = prepare_work_percent_by_caregiver_role_and_type_chart( - work_by_caregiver_role_and_type_with_percent + work_by_caregiver_role_and_type_with_percent = ( + get_total_hours_by_role_and_work_type_with_percent() + ) + context[ + "work_percent_by_caregiver_role_and_type_chart" + ] = prepare_work_percent_by_caregiver_role_and_type_chart( + work_by_caregiver_role_and_type_with_percent, ) - context["work_by_caregiver_role_and_type_chart"] = prepare_work_by_caregiver_role_and_type_chart( - work_by_caregiver_role_and_type_with_percent + context[ + "work_by_caregiver_role_and_type_chart" + ] = prepare_work_by_caregiver_role_and_type_chart( + work_by_caregiver_role_and_type_with_percent, ) return context - def get_context_data(self, **kwargs: Any) -> Dict[str, Any]: + def get_context_data(self, **kwargs: Any) -> dict[str, Any]: context = super().get_context_data(**kwargs) # Check if work has been recorded