diff --git a/Pipfile b/Pipfile index ba75c4e5..c100cbb6 100644 --- a/Pipfile +++ b/Pipfile @@ -25,6 +25,7 @@ uritemplate = "*" whitenoise = "*" django-taggit = "*" django-qr-code = "*" +django-dbsettings = "*" [dev-packages] coveralls = "*" diff --git a/babybuddy/forms.py b/babybuddy/forms.py index 004db117..5fb82d60 100644 --- a/babybuddy/forms.py +++ b/babybuddy/forms.py @@ -6,22 +6,7 @@ from django.contrib.auth.forms import PasswordChangeForm, UserCreationForm from django.contrib.auth.models import Group from django.utils.translation import gettext_lazy as _ -from core.widgets import TimeInput - -from .models import Settings, SiteSettings - - -class SiteSettingsForm(forms.ModelForm): - class Meta: - model = SiteSettings - fields = [ - "nap_start_min", - "nap_start_max", - ] - widgets = { - "nap_start_min": TimeInput(), - "nap_start_max": TimeInput(), - } +from .models import Settings class BabyBuddyUserForm(forms.ModelForm): diff --git a/babybuddy/migrations/0028_sitesettings.py b/babybuddy/migrations/0028_sitesettings.py deleted file mode 100644 index 5433643e..00000000 --- a/babybuddy/migrations/0028_sitesettings.py +++ /dev/null @@ -1,44 +0,0 @@ -# Generated by Django 4.0.7 on 2022-08-14 23:28 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ("babybuddy", "0027_remove_standard_group"), - ] - - operations = [ - migrations.CreateModel( - name="SiteSettings", - fields=[ - ( - "id", - models.AutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "nap_start_min", - models.TimeField( - blank=True, - help_text="The minimum default time that a sleep entry is consider a nap.If set the nap property will be preselected if the starttime is within the bounds.", - null=True, - verbose_name="Default minimum nap start time", - ), - ), - ( - "nap_start_max", - models.TimeField( - blank=True, - help_text="The maximum default time that a sleep entry is consider a nap.If set the nap property will be preselected if the starttime is within the bounds.", - null=True, - verbose_name="Default maximum nap start time", - ), - ), - ], - ), - ] diff --git a/babybuddy/models.py b/babybuddy/models.py index 1a44a55f..6ca19159 100644 --- a/babybuddy/models.py +++ b/babybuddy/models.py @@ -94,32 +94,6 @@ class Settings(models.Model): return None -class SiteSettings(models.Model): - nap_start_min = models.TimeField( - verbose_name=_("Default minimum nap start time"), - help_text=_( - "The minimum default time that a sleep entry is consider a nap." - "If set the nap property will be preselected if the start" - "time is within the bounds." - ), - blank=True, - null=True, - ) - nap_start_max = models.TimeField( - verbose_name=_("Default maximum nap start time"), - help_text=_( - "The maximum default time that a sleep entry is consider a nap." - "If set the nap property will be preselected if the start" - "time is within the bounds." - ), - blank=True, - null=True, - ) - - def __str__(self): - return _("Site Settings") - - @receiver(post_save, sender=get_user_model()) def create_user_settings(sender, instance, created, **kwargs): if created: diff --git a/babybuddy/settings/base.py b/babybuddy/settings/base.py index bb80554a..285a1abf 100644 --- a/babybuddy/settings/base.py +++ b/babybuddy/settings/base.py @@ -37,6 +37,7 @@ INSTALLED_APPS = [ "storages", "import_export", "qr_code", + "dbsettings", "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", @@ -353,7 +354,5 @@ DEFAULT_AUTO_FIELD = "django.db.models.AutoField" BABY_BUDDY = { "ALLOW_UPLOADS": bool(strtobool(os.environ.get("ALLOW_UPLOADS") or "True")), - "NAP_START_MAX": os.environ.get("NAP_START_MAX") or "18:00", - "NAP_START_MIN": os.environ.get("NAP_START_MIN") or "06:00", "READ_ONLY_GROUP_NAME": "read_only", } diff --git a/babybuddy/site_settings.py b/babybuddy/site_settings.py new file mode 100644 index 00000000..92a58bdf --- /dev/null +++ b/babybuddy/site_settings.py @@ -0,0 +1,29 @@ +# -*- coding: utf-8 -*- +from datetime import time + +from django.utils.translation import gettext_lazy as _ + +import dbsettings + +from .widgets import TimeInput + + +class NapSettings(dbsettings.Group): + nap_start_min = dbsettings.TimeValue( + default=time(6), + description=_("Default minimum nap start time"), + help_text=_( + "The minimum default time that a sleep entry is consider a nap. If set the " + "nap property will be preselected if the start time is within the bounds." + ), + widget=TimeInput, + ) + nap_start_max = dbsettings.TimeValue( + default=time(18), + description=_("Default maximum nap start time"), + help_text=_( + "The maximum default time that a sleep entry is consider a nap. If set the " + "nap property will be preselected if the start time is within the bounds." + ), + widget=TimeInput, + ) diff --git a/babybuddy/templates/babybuddy/nav-dropdown.html b/babybuddy/templates/babybuddy/nav-dropdown.html index 907d614e..efc2e04d 100644 --- a/babybuddy/templates/babybuddy/nav-dropdown.html +++ b/babybuddy/templates/babybuddy/nav-dropdown.html @@ -336,7 +336,7 @@ {% trans "API Browser" %} {% if request.user.is_staff %} - {% trans "Settings" %} {% trans "Users" %} diff --git a/babybuddy/urls.py b/babybuddy/urls.py index d05d0895..3cd7d579 100644 --- a/babybuddy/urls.py +++ b/babybuddy/urls.py @@ -35,7 +35,6 @@ app_patterns = [ name="password_reset_complete", ), path("", views.RootRouter.as_view(), name="root-router"), - path("settings/", views.SiteSettingsUpdate.as_view(), name="site-settings-update"), path("welcome/", views.Welcome.as_view(), name="welcome"), path("users/", views.UserList.as_view(), name="user-list"), path("users/add/", views.UserAdd.as_view(), name="user-add"), @@ -45,6 +44,7 @@ app_patterns = [ path("user/password/", views.UserPassword.as_view(), name="user-password"), path("user/settings/", views.UserSettings.as_view(), name="user-settings"), path("user/add-device/", views.UserAddDevice.as_view(), name="user-add-device"), + path("settings/", include("dbsettings.urls")), ] urlpatterns = [ diff --git a/babybuddy/views.py b/babybuddy/views.py index 8c7e3ce6..4d2b85c6 100644 --- a/babybuddy/views.py +++ b/babybuddy/views.py @@ -37,7 +37,6 @@ from django_filters.views import FilterView from babybuddy import forms from babybuddy.mixins import LoginRequiredMixin, PermissionRequiredMixin, StaffOnlyMixin -from babybuddy.models import SiteSettings def csrf_failure(request, reason=""): @@ -96,17 +95,6 @@ class LogoutView(LogoutViewBase): pass -class SiteSettingsUpdate(StaffOnlyMixin, SuccessMessageMixin, UpdateView): - model = SiteSettings - template_name = "babybuddy/site_settings_form.html" - form_class = forms.SiteSettingsForm - success_url = reverse_lazy("babybuddy:site-settings-update") - success_message = gettext_lazy("Site settings updated.") - - def get_object(self, queryset=None): - return SiteSettings.objects.get_or_create(pk=1)[0] - - class UserList(StaffOnlyMixin, BabyBuddyFilterView): model = get_user_model() template_name = "babybuddy/user_list.html" diff --git a/babybuddy/widgets.py b/babybuddy/widgets.py new file mode 100644 index 00000000..682ca63e --- /dev/null +++ b/babybuddy/widgets.py @@ -0,0 +1,29 @@ +import datetime + +from django.forms import widgets + + +class DateTimeBaseInput(widgets.DateTimeBaseInput): + def format_value(self, value): + if isinstance(value, datetime.datetime): + value = value.isoformat() + return value + + +class DateTimeInput(DateTimeBaseInput): + input_type = "datetime-local" + + def build_attrs(self, base_attrs, extra_attrs=None): + attrs = super().build_attrs(base_attrs, extra_attrs) + # Default to seconds granularity. Required for client validation in Safari. + if "step" not in attrs: + attrs["step"] = 1 + return attrs + + +class DateInput(DateTimeBaseInput): + input_type = "date" + + +class TimeInput(DateTimeBaseInput): + input_type = "time" diff --git a/core/forms.py b/core/forms.py index 06b49602..35aed650 100644 --- a/core/forms.py +++ b/core/forms.py @@ -7,8 +7,9 @@ from django.utils.translation import gettext as _ from taggit.forms import TagField +from babybuddy.widgets import DateInput, DateTimeInput from core import models -from core.widgets import TagsEditor, ChildRadioSelect, DateInput, DateTimeInput +from core.widgets import TagsEditor, ChildRadioSelect def set_initial_values(kwargs, form_type): diff --git a/core/models.py b/core/models.py index e6b77c25..82489430 100644 --- a/core/models.py +++ b/core/models.py @@ -2,7 +2,6 @@ import re from datetime import timedelta -from django.conf import settings from django.core.cache import cache from django.core.exceptions import ValidationError from django.core.validators import RegexValidator @@ -13,6 +12,7 @@ from django.utils.translation import gettext_lazy as _ from taggit.managers import TaggableManager as TaggitTaggableManager from taggit.models import GenericTaggedItemBase, TagBase +from babybuddy.site_settings import NapSettings from core.utils import random_color @@ -477,6 +477,7 @@ class Sleep(models.Model): objects = models.Manager() naps = NapsManager() + settings = NapSettings(_("Nap settings")) class Meta: default_permissions = ("view", "add", "change", "delete") @@ -489,14 +490,12 @@ class Sleep(models.Model): @property def nap(self): - nap_start_min = timezone.datetime.strptime( - settings.BABY_BUDDY["NAP_START_MIN"], "%H:%M" - ).time() - nap_start_max = timezone.datetime.strptime( - settings.BABY_BUDDY["NAP_START_MAX"], "%H:%M" - ).time() local_start_time = timezone.localtime(self.start).time() - return nap_start_min <= local_start_time <= nap_start_max + return ( + Sleep.settings.nap_start_min + <= local_start_time + <= Sleep.settings.nap_start_max + ) def save(self, *args, **kwargs): if self.start and self.end: diff --git a/core/widgets.py b/core/widgets.py index 834d092d..3fe1ed67 100644 --- a/core/widgets.py +++ b/core/widgets.py @@ -104,29 +104,3 @@ class ChildRadioSelect(RadioSelect): if value != "": option["picture"] = value.instance.picture return option - - -class DateTimeBaseInput(widgets.DateTimeBaseInput): - def format_value(self, value): - if isinstance(value, datetime.datetime): - value = value.isoformat() - return value - - -class DateTimeInput(DateTimeBaseInput): - input_type = "datetime-local" - - def build_attrs(self, base_attrs, extra_attrs=None): - attrs = super().build_attrs(base_attrs, extra_attrs) - # Default to seconds granularity. Required for client validation in Safari. - if "step" not in attrs: - attrs["step"] = 1 - return attrs - - -class DateInput(DateTimeBaseInput): - input_type = "date" - - -class TimeInput(DateTimeBaseInput): - input_type = "time" diff --git a/docs/configuration/application.md b/docs/configuration/application.md index 9a9f2ab5..7461c218 100644 --- a/docs/configuration/application.md +++ b/docs/configuration/application.md @@ -9,20 +9,6 @@ for exceptions. This setting should be *False* in production deployments. See also [Django's documentation on the DEBUG setting](https://docs.djangoproject.com/en/4.0/ref/settings/#debug). -## `NAP_START_MAX` - -*Default:* `18:00` - -The maximum nap *start* time (in the instance's time zone). Expects the 24-hour -format %H:%M. - -## `NAP_START_MIN` - -*Default:* `06:00` - -The minimum nap *start* time (in the instance's time zone). Expects the 24-hour -format %H:%M. - ## `SUB_PATH` *Default:* `None`