Update to Django 5.x (#746)

This commit is contained in:
Christopher Charbonneau Wells 2024-01-13 20:22:08 -08:00 committed by GitHub
parent 9b603a9e84
commit 49d8f2b340
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 122 additions and 98 deletions

View File

@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
strategy: strategy:
matrix: matrix:
python-version: [ '3.8', '3.9', '3.10', '3.11', '3.12' ] python-version: [ '3.10', '3.11', '3.12' ]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
- uses: ./.github/actions/setup - uses: ./.github/actions/setup

View File

@ -6,7 +6,7 @@ name = "pypi"
[packages] [packages]
boto3 = "*" boto3 = "*"
dj-database-url = "*" dj-database-url = "*"
django = "~=4.0" django = "~=5.0"
django-axes = "*" django-axes = "*"
django-filter = "*" django-filter = "*"
django-imagekit = "*" django-imagekit = "*"

View File

@ -316,8 +316,8 @@ class FeedingAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
{ {
"id": 3, "id": 3,
"child": 1, "child": 1,
"start": "2017-11-18T19:00:00-05:00", "start": "2017-11-18T09:00:00-05:00",
"end": "2017-11-18T19:15:00-05:00", "end": "2017-11-18T09:15:00-05:00",
"duration": "00:15:00", "duration": "00:15:00",
"type": "formula", "type": "formula",
"method": "bottle", "method": "bottle",
@ -335,7 +335,7 @@ class FeedingAPITestCase(TestBase.BabyBuddyAPITestCaseBase):
def test_get_with_iso_filter(self): def test_get_with_iso_filter(self):
response = self.client.get( response = self.client.get(
self.endpoint, {"start_min": "2017-11-18T11:30:00-05:00"} self.endpoint, {"start_min": "2017-11-18T04:00:00-05:00"}
) )
self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data["count"], 3) self.assertEqual(response.data["count"], 3)

View File

@ -249,8 +249,8 @@
"fields": "fields":
{ {
"child": 1, "child": 1,
"start": "2017-11-18T14:00:00-05:00", "start": "2017-11-18T09:00:00Z",
"end": "2017-11-18T14:30:00-05:00", "end": "2017-11-18T09:30:00Z",
"duration": "00:30:00", "duration": "00:30:00",
"type": "breast milk", "type": "breast milk",
"method": "left breast", "method": "left breast",
@ -263,8 +263,8 @@
"fields": "fields":
{ {
"child": 1, "child": 1,
"start": "2017-11-18T16:30:00-05:00", "start": "2017-11-18T11:30:00Z",
"end": "2017-11-18T17:00:00-05:00", "end": "2017-11-18T12:00:00Z",
"duration": "00:30:00", "duration": "00:30:00",
"type": "breast milk", "type": "breast milk",
"method": "right breast", "method": "right breast",
@ -277,8 +277,8 @@
"fields": "fields":
{ {
"child": 1, "child": 1,
"start": "2017-11-18T19:00:00-05:00", "start": "2017-11-18T14:00:00Z",
"end": "2017-11-18T19:15:00-05:00", "end": "2017-11-18T14:15:00Z",
"duration": "00:15:00", "duration": "00:15:00",
"type": "formula", "type": "formula",
"method": "bottle", "method": "bottle",
@ -292,8 +292,8 @@
"fields": "fields":
{ {
"child": 1, "child": 1,
"start": "2017-11-17T19:00:00-05:00", "start": "2017-11-17T14:00:00Z",
"end": "2017-11-17T19:15:00-05:00", "end": "2017-11-17T14:15:00Z",
"duration": "00:15:00", "duration": "00:15:00",
"type": "formula", "type": "formula",
"method": "bottle", "method": "bottle",
@ -307,8 +307,8 @@
"fields": "fields":
{ {
"child": 1, "child": 1,
"start": "2017-11-11T19:00:00-05:00", "start": "2017-11-11T14:00:00Z",
"end": "2017-11-11T19:15:00-05:00", "end": "2017-11-11T14:15:00Z",
"duration": "00:15:00", "duration": "00:15:00",
"type": "formula", "type": "formula",
"method": "bottle", "method": "bottle",
@ -322,8 +322,8 @@
"fields": "fields":
{ {
"child": 1, "child": 1,
"start": "2017-11-11T00:00:00-05:00", "start": "2017-11-11T05:00:00Z",
"end": "2017-11-11T00:15:00-05:00", "end": "2017-11-11T05:15:00Z",
"duration": "00:15:00", "duration": "00:15:00",
"type": "formula", "type": "formula",
"method": "bottle", "method": "bottle",

View File

@ -2,7 +2,6 @@ from os import getenv
from time import time from time import time
from functools import wraps from functools import wraps
import pytz
from urllib.parse import urlunsplit, urlsplit from urllib.parse import urlunsplit, urlsplit
from django.conf import settings from django.conf import settings
@ -55,8 +54,8 @@ class UserTimezoneMiddleware:
user = request.user user = request.user
if hasattr(user, "settings") and user.settings.timezone: if hasattr(user, "settings") and user.settings.timezone:
try: try:
timezone.activate(pytz.timezone(user.settings.timezone)) timezone.activate(user.settings.timezone)
except pytz.UnknownTimeZoneError: except ValueError:
pass pass
return self.get_response(request) return self.get_response(request)

View File

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import pytz import zoneinfo
from django.conf import settings from django.conf import settings
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
@ -63,7 +63,9 @@ class Settings(models.Model):
verbose_name=_("Language"), verbose_name=_("Language"),
) )
timezone = models.CharField( timezone = models.CharField(
choices=tuple(zip(pytz.common_timezones, pytz.common_timezones)), choices=sorted(
tuple(zip(zoneinfo.available_timezones(), zoneinfo.available_timezones()))
),
default=timezone.get_default_timezone_name(), default=timezone.get_default_timezone_name(),
max_length=100, max_length=100,
verbose_name=_("Timezone"), verbose_name=_("Timezone"),

View File

@ -20,7 +20,7 @@ DEBUG = bool(strtobool(os.environ.get("DEBUG") or "False"))
# Applications # Applications
# https://docs.djangoproject.com/en/4.0/ref/applications/ # https://docs.djangoproject.com/en/5.0/ref/applications/
INSTALLED_APPS = [ INSTALLED_APPS = [
"api", "api",
@ -48,7 +48,7 @@ INSTALLED_APPS = [
] ]
# Middleware # Middleware
# https://docs.djangoproject.com/en/4.0/ref/middleware/ # https://docs.djangoproject.com/en/5.0/ref/middleware/
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
@ -69,13 +69,13 @@ MIDDLEWARE = [
# URL dispatcher # URL dispatcher
# https://docs.djangoproject.com/en/4.0/topics/http/urls/ # https://docs.djangoproject.com/en/5.0/topics/http/urls/
ROOT_URLCONF = "babybuddy.urls" ROOT_URLCONF = "babybuddy.urls"
# Templates # Templates
# https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-TEMPLATES # https://docs.djangoproject.com/en/5.0/ref/settings/#std:setting-TEMPLATES
TEMPLATES = [ TEMPLATES = [
{ {
@ -95,7 +95,7 @@ TEMPLATES = [
# Database # Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases # https://docs.djangoproject.com/en/5.0/ref/settings/#databases
config = { config = {
"ENGINE": os.getenv("DB_ENGINE") or "django.db.backends.sqlite3", "ENGINE": os.getenv("DB_ENGINE") or "django.db.backends.sqlite3",
@ -118,7 +118,7 @@ DATABASES = {"default": config}
# Cache # Cache
# https://docs.djangoproject.com/en/4.0/topics/cache/ # https://docs.djangoproject.com/en/5.0/topics/cache/
CACHES = { CACHES = {
"default": { "default": {
@ -129,13 +129,13 @@ CACHES = {
# WGSI # WGSI
# https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/ # https://docs.djangoproject.com/en/5.0/howto/deployment/wsgi/
WSGI_APPLICATION = "babybuddy.wsgi.application" WSGI_APPLICATION = "babybuddy.wsgi.application"
# Authentication # Authentication
# https://docs.djangoproject.com/en/4.0/topics/auth/default/ # https://docs.djangoproject.com/en/5.0/topics/auth/default/
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
"axes.backends.AxesBackend", "axes.backends.AxesBackend",
@ -158,14 +158,14 @@ if REVERSE_PROXY_AUTH:
# Timezone # Timezone
# https://docs.djangoproject.com/en/4.0/topics/i18n/timezones/ # https://docs.djangoproject.com/en/5.0/topics/i18n/timezones/
USE_TZ = True USE_TZ = True
TIME_ZONE = "UTC" TIME_ZONE = "UTC"
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/4.0/topics/i18n/ # https://docs.djangoproject.com/en/5.0/topics/i18n/
USE_I18N = True USE_I18N = True
@ -199,18 +199,26 @@ LANGUAGES = [
# Format localization # Format localization
# https://docs.djangoproject.com/en/4.0/topics/i18n/formatting/ # https://docs.djangoproject.com/en/5.0/topics/i18n/formatting/
USE_L10N = True
FORMAT_MODULE_PATH = ["babybuddy.formats"] FORMAT_MODULE_PATH = ["babybuddy.formats"]
# Static files (CSS, JavaScript, Images) # Storage
# https://docs.djangoproject.com/en/4.0/howto/static-files/ # https://docs.djangoproject.com/en/5.0/ref/files/storage/
# http://whitenoise.evans.io/en/stable/django.html # https://docs.djangoproject.com/en/5.0/ref/settings/#std-setting-STORAGES
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" STORAGES = {
"default": {"BACKEND": "django.core.files.storage.FileSystemStorage"},
"staticfiles": {
"BACKEND": "whitenoise.storage.CompressedManifestStaticFilesStorage",
},
}
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/
# http://whitenoise.evans.io/en/stable/django.html
STATICFILES_FINDERS = [ STATICFILES_FINDERS = [
"django.contrib.staticfiles.finders.FileSystemFinder", "django.contrib.staticfiles.finders.FileSystemFinder",
@ -225,7 +233,7 @@ WHITENOISE_ROOT = os.path.join(BASE_DIR, "static", "babybuddy", "root")
# Media files (User uploaded content) # Media files (User uploaded content)
# https://docs.djangoproject.com/en/4.0/topics/files/ # https://docs.djangoproject.com/en/5.0/topics/files/
MEDIA_ROOT = os.path.join(BASE_DIR, "media") MEDIA_ROOT = os.path.join(BASE_DIR, "media")
@ -240,11 +248,11 @@ AWS_SECRET_ACCESS_KEY = os.environ.get("AWS_SECRET_ACCESS_KEY") or None
AWS_S3_ENDPOINT_URL = os.environ.get("AWS_S3_ENDPOINT_URL") or None AWS_S3_ENDPOINT_URL = os.environ.get("AWS_S3_ENDPOINT_URL") or None
if AWS_STORAGE_BUCKET_NAME: if AWS_STORAGE_BUCKET_NAME:
DEFAULT_FILE_STORAGE = "storages.backends.s3boto3.S3Boto3Storage" STORAGES["default"]["BACKEND"] = "storages.backends.s3boto3.S3Boto3Storage"
# Email # Email
# https://docs.djangoproject.com/en/4.0/topics/email/ # https://docs.djangoproject.com/en/5.0/topics/email/
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
EMAIL_SUBJECT_PREFIX = "[Baby Buddy] " EMAIL_SUBJECT_PREFIX = "[Baby Buddy] "
@ -263,17 +271,17 @@ if os.environ.get("EMAIL_HOST"):
# Security # Security
# https://docs.djangoproject.com/en/4.0/ref/settings/#secure-proxy-ssl-header # https://docs.djangoproject.com/en/5.0/ref/settings/#secure-proxy-ssl-header
if os.environ.get("SECURE_PROXY_SSL_HEADER"): if os.environ.get("SECURE_PROXY_SSL_HEADER"):
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https") SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
# https://docs.djangoproject.com/en/4.0/topics/http/sessions/#settings # https://docs.djangoproject.com/en/5.0/topics/http/sessions/#settings
SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = bool( SESSION_COOKIE_SECURE = bool(
strtobool(os.environ.get("SESSION_COOKIE_SECURE") or "False") strtobool(os.environ.get("SESSION_COOKIE_SECURE") or "False")
) )
# https://docs.djangoproject.com/en/4.0/ref/csrf/#settings # https://docs.djangoproject.com/en/5.0/ref/csrf/#settings
CSRF_COOKIE_HTTPONLY = True CSRF_COOKIE_HTTPONLY = True
CSRF_COOKIE_SECURE = bool(strtobool(os.environ.get("CSRF_COOKIE_SECURE") or "False")) CSRF_COOKIE_SECURE = bool(strtobool(os.environ.get("CSRF_COOKIE_SECURE") or "False"))
CSRF_FAILURE_VIEW = "babybuddy.views.csrf_failure" CSRF_FAILURE_VIEW = "babybuddy.views.csrf_failure"
@ -282,7 +290,7 @@ CSRF_TRUSTED_ORIGINS = list(
) )
# https://docs.djangoproject.com/en/4.0/topics/auth/passwords/ # https://docs.djangoproject.com/en/5.0/topics/auth/passwords/
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
@ -349,7 +357,7 @@ AXES_LOCKOUT_URL = "/login/lock"
ROLLING_SESSION_REFRESH = 86400 ROLLING_SESSION_REFRESH = 86400
# Set default auto field for models. # Set default auto field for models.
# See https://docs.djangoproject.com/en/4.0/releases/3.2/#customizing-type-of-auto-created-primary-keys # See https://docs.djangoproject.com/en/5.0/releases/3.2/#customizing-type-of-auto-created-primary-keys
DEFAULT_AUTO_FIELD = "django.db.models.AutoField" DEFAULT_AUTO_FIELD = "django.db.models.AutoField"

View File

@ -4,6 +4,8 @@ SECRET_KEY = "CISECRETKEYIGUESS"
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/ # https://docs.djangoproject.com/en/5.0/howto/static-files/
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage" STORAGES["staticfiles"][
"BACKEND"
] = "django.contrib.staticfiles.storage.StaticFilesStorage"

View File

@ -1,20 +1,22 @@
from .base import * from .base import *
# Quick-start development settings - unsuitable for production # Quick-start development settings - unsuitable for production
# https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/ # https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/
SECRET_KEY = os.environ.get("SECRET_KEY") or "DEVELOPMENT!!" SECRET_KEY = os.environ.get("SECRET_KEY") or "DEVELOPMENT!!"
DEBUG = bool(strtobool(os.environ.get("DEBUG") or "True")) DEBUG = bool(strtobool(os.environ.get("DEBUG") or "True"))
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.0/howto/static-files/ # https://docs.djangoproject.com/en/5.0/howto/static-files/
# #
# Comment out STATICFILES_STORAGE and uncomment DEBUG = False to test with # Comment out STORAGES["staticfiles"]["BACKEND"] and uncomment DEBUG = False to
# production static files. # test with production static files.
# DEBUG = False # DEBUG = False
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage" STORAGES["staticfiles"][
"BACKEND"
] = "django.contrib.staticfiles.storage.StaticFilesStorage"
# Django Rest Framework # Django Rest Framework

View File

@ -1,7 +1,7 @@
from .development import * from .development import *
# CSRF configuration # CSRF configuration
# https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-CSRF_TRUSTED_ORIGINS # https://docs.djangoproject.com/en/5.0/ref/settings/#std:setting-CSRF_TRUSTED_ORIGINS
# https://www.gitpod.io/docs/environment-variables/#default-environment-variables # https://www.gitpod.io/docs/environment-variables/#default-environment-variables
CSRF_TRUSTED_ORIGINS = [ CSRF_TRUSTED_ORIGINS = [

View File

@ -8,7 +8,7 @@ SECRET_KEY = ""
ALLOWED_HOSTS = [""] ALLOWED_HOSTS = [""]
# Database # Database
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases # https://docs.djangoproject.com/en/5.0/ref/settings/#databases
DATABASES = { DATABASES = {
"default": { "default": {
@ -18,7 +18,7 @@ DATABASES = {
} }
# Media files # Media files
# https://docs.djangoproject.com/en/4.0/topics/files/ # https://docs.djangoproject.com/en/5.0/topics/files/
MEDIA_ROOT = os.path.join(BASE_DIR, "../data/media") MEDIA_ROOT = os.path.join(BASE_DIR, "../data/media")
@ -26,8 +26,8 @@ MEDIA_ROOT = os.path.join(BASE_DIR, "../data/media")
# After setting up SSL, uncomment the settings below for enhanced security of # After setting up SSL, uncomment the settings below for enhanced security of
# application cookies. # application cookies.
# #
# See https://docs.djangoproject.com/en/4.0/topics/http/sessions/#settings # See https://docs.djangoproject.com/en/5.0/topics/http/sessions/#settings
# See https://docs.djangoproject.com/en/4.0/ref/csrf/#settings # See https://docs.djangoproject.com/en/5.0/ref/csrf/#settings
# SESSION_COOKIE_SECURE = True # SESSION_COOKIE_SECURE = True
# CSRF_COOKIE_SECURE = True # CSRF_COOKIE_SECURE = True

View File

@ -3,15 +3,15 @@ from .base import *
SECRET_KEY = "TESTS" SECRET_KEY = "TESTS"
# Password hasher configuration # Password hasher configuration
# See https://docs.djangoproject.com/en/4.0/ref/settings/#password-hashers # See https://docs.djangoproject.com/en/5.0/ref/settings/#password-hashers
# See https://docs.djangoproject.com/en/4.0/topics/testing/overview/#password-hashing # See https://docs.djangoproject.com/en/5.0/topics/testing/overview/#password-hashing
PASSWORD_HASHERS = [ PASSWORD_HASHERS = [
"django.contrib.auth.hashers.MD5PasswordHasher", "django.contrib.auth.hashers.MD5PasswordHasher",
] ]
# Email # Email
# https://docs.djangoproject.com/en/4.0/topics/email/ # https://docs.djangoproject.com/en/5.0/topics/email/
EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend" EMAIL_BACKEND = "django.core.mail.backends.locmem.EmailBackend"

View File

@ -65,8 +65,7 @@ class FormsTestCase(TestCase):
page = self.c.post("/user/password/", params) page = self.c.post("/user/password/", params)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError( self.assertFormError(
page, page.context["form"],
"form",
"old_password", "old_password",
"Your old password was entered incorrectly. " "Please enter it again.", "Your old password was entered incorrectly. " "Please enter it again.",
) )
@ -75,7 +74,9 @@ class FormsTestCase(TestCase):
page = self.c.post("/user/password/", params) page = self.c.post("/user/password/", params)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError( self.assertFormError(
page, "form", "new_password2", "The two password fields didnt match." page.context["form"],
"new_password2",
"The two password fields didnt match.",
) )
params["new_password2"] = "mynewpassword" params["new_password2"] = "mynewpassword"
@ -101,7 +102,7 @@ class FormsTestCase(TestCase):
page = self.c.post("/users/{}/delete/".format(new_user.id)) page = self.c.post("/users/{}/delete/".format(new_user.id))
self.assertEqual(page.status_code, 302) self.assertEqual(page.status_code, 302)
self.assertQuerysetEqual( self.assertQuerySetEqual(
get_user_model().objects.filter(username="username"), [] get_user_model().objects.filter(username="username"), []
) )
@ -212,7 +213,9 @@ class FormsTestCase(TestCase):
page = self.c.post("/user/settings/", params) page = self.c.post("/user/settings/", params)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, "user_form", "email", "Enter a valid email address.") self.assertFormError(
page.context["user_form"], "email", "Enter a valid email address."
)
def test_user_settings_language(self): def test_user_settings_language(self):
self.c.login(**self.credentials) self.c.login(**self.credentials)

View File

@ -210,7 +210,7 @@ class ChildFormsTestCase(FormsTestCaseBase):
) )
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError( self.assertFormError(
page, "form", "confirm_name", "Name does not match child name." page.context["form"], "confirm_name", "Name does not match child name."
) )
params["confirm_name"] = str(self.child) params["confirm_name"] = str(self.child)
@ -798,7 +798,9 @@ class ValidationsTestCase(FormsTestCaseBase):
page = self.c.post("/weight/{}/".format(entry.id), params, follow=True) page = self.c.post("/weight/{}/".format(entry.id), params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, "form", "date", "Date can not be in the future.") self.assertFormError(
page.context["form"], "date", "Date can not be in the future."
)
def test_validate_duration(self): def test_validate_duration(self):
end = timezone.localtime() - timezone.timedelta(minutes=10) end = timezone.localtime() - timezone.timedelta(minutes=10)
@ -813,14 +815,14 @@ class ValidationsTestCase(FormsTestCaseBase):
page = self.c.post("/tummy-time/add/", params, follow=True) page = self.c.post("/tummy-time/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError( self.assertFormError(
page, "form", None, "Start time must come before end time." page.context["form"], None, "Start time must come before end time."
) )
start = end - timezone.timedelta(weeks=53) start = end - timezone.timedelta(weeks=53)
params["start"] = self.localtime_string(start) params["start"] = self.localtime_string(start)
page = self.c.post("/tummy-time/add/", params, follow=True) page = self.c.post("/tummy-time/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, "form", None, "Duration too long.") self.assertFormError(page.context["form"], None, "Duration too long.")
def test_validate_time(self): def test_validate_time(self):
future = timezone.localtime() + timezone.timedelta(hours=1) future = timezone.localtime() + timezone.timedelta(hours=1)
@ -833,7 +835,9 @@ class ValidationsTestCase(FormsTestCaseBase):
page = self.c.post("/tummy-time/add/", params, follow=True) page = self.c.post("/tummy-time/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError(page, "form", "end", "Date/time can not be in the future.") self.assertFormError(
page.context["form"], "end", "Date/time can not be in the future."
)
def test_validate_unique_period(self): def test_validate_unique_period(self):
entry = models.TummyTime.objects.create( entry = models.TummyTime.objects.create(
@ -854,7 +858,9 @@ class ValidationsTestCase(FormsTestCaseBase):
page = self.c.post("/tummy-time/add/", params, follow=True) page = self.c.post("/tummy-time/add/", params, follow=True)
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
self.assertFormError( self.assertFormError(
page, "form", None, "Another entry intersects the specified time period." page.context["form"],
None,
"Another entry intersects the specified time period.",
) )

View File

@ -24,8 +24,9 @@ class ImportTestCase(TestCase):
).save() ).save()
def get_dataset(self, model_name): def get_dataset(self, model_name):
file = open(self.base_path + model_name + ".csv") with open(self.base_path + model_name + ".csv", "r") as f:
return tablib.Dataset().load(file.read()) data = f.read()
return tablib.Dataset().load(data)
def import_data(self, model, count): def import_data(self, model, count):
dataset = self.get_dataset(model._meta.model_name) dataset = self.get_dataset(model._meta.model_name)
@ -86,7 +87,7 @@ class ImportTestCase(TestCase):
] ]
for pk, tags in tests: for pk, tags in tests:
entry = models.Temperature.objects.get(pk=pk) entry = models.Temperature.objects.get(pk=pk)
self.assertQuerysetEqual(entry.tags.names(), tags, ordered=False) self.assertQuerySetEqual(entry.tags.names(), tags, ordered=False)
def test_temperature(self): def test_temperature(self):
self.import_data(models.Temperature, 23) self.import_data(models.Temperature, 23)

View File

@ -1,6 +1,4 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import pytz
from django.contrib.auth import get_user_model from django.contrib.auth import get_user_model
from django.test import TestCase from django.test import TestCase
from django.utils import timezone from django.utils import timezone
@ -28,7 +26,7 @@ class TemplateTagsTestCase(TestCase):
# Ensure timezone matches the one defined by fixtures. # Ensure timezone matches the one defined by fixtures.
user_timezone = Settings.objects.first().timezone user_timezone = Settings.objects.first().timezone
timezone.activate(pytz.timezone(user_timezone)) timezone.activate(user_timezone)
# Test file data uses a basis date of 2017-11-18. # Test file data uses a basis date of 2017-11-18.
date = timezone.localtime().strptime("2017-11-18", "%Y-%m-%d") date = timezone.localtime().strptime("2017-11-18", "%Y-%m-%d")
@ -160,9 +158,11 @@ class TemplateTagsTestCase(TestCase):
def test_card_feeding_recent(self): def test_card_feeding_recent(self):
data = cards.card_feeding_recent(self.context, self.child, self.date) data = cards.card_feeding_recent(self.context, self.child, self.date)
self.assertEqual(data["type"], "feeding") self.assertEqual(data["type"], "feeding")
self.assertFalse(data["empty"]) self.assertFalse(data["empty"])
self.assertFalse(data["hide_empty"]) self.assertFalse(data["hide_empty"])
# most recent day # most recent day
self.assertEqual(data["feedings"][0]["total"], 2.5) self.assertEqual(data["feedings"][0]["total"], 2.5)
self.assertEqual(data["feedings"][0]["count"], 3) self.assertEqual(data["feedings"][0]["count"], 3)
@ -273,7 +273,7 @@ class TemplateTagsTestCase(TestCase):
}, },
{ {
"type": "duration", "type": "duration",
"stat": timezone.timedelta(days=1, seconds=46980), "stat": timezone.timedelta(days=1, seconds=39780),
"title": "Feeding frequency", "title": "Feeding frequency",
}, },
{ {

View File

@ -7,7 +7,7 @@ information and steps below to set up a local development environment for Baby B
## Requirements ## Requirements
- Python 3.8+, pip, pipenv - Python 3.10+, pip, pipenv
- NodeJS 18.x and NPM 8.x (NVM recommended) - NodeJS 18.x and NPM 8.x (NVM recommended)
- Gulp - Gulp
- Possibly `libpq-dev` - Possibly `libpq-dev`

View File

@ -117,14 +117,14 @@ requirements are Python, a web server, an application server, and a database.
### Requirements ### Requirements
- Python 3.8+, pip, pipenv - Python 3.10+, pip, pipenv
- Web server ([nginx](http://nginx.org/), [Apache](http://httpd.apache.org/), etc.) - Web server ([nginx](http://nginx.org/), [Apache](http://httpd.apache.org/), etc.)
- Application server ([uwsgi](http://projects.unbit.it/uwsgi), [gunicorn](http://gunicorn.org/), etc.) - Application server ([uwsgi](http://projects.unbit.it/uwsgi), [gunicorn](http://gunicorn.org/), etc.)
- Database (See [Django's databases documentation](https://docs.djangoproject.com/en/4.2/ref/databases/)). - Database (See [Django's databases documentation](https://docs.djangoproject.com/en/4.2/ref/databases/)).
### Example deployment ### Example deployment
*This example assumes a 1 GB VPS instance with Ubuntu 20.04.* It uses Python 3.8, *This example assumes a 1 GB VPS instance with Ubuntu 20.04.* It uses Python 3.10,
nginx, uwsgi and sqlite. It should be sufficient for a few users (e.g., two parents nginx, uwsgi and sqlite. It should be sufficient for a few users (e.g., two parents
and any number of children). and any number of children).

View File

@ -202,6 +202,7 @@ function test(cb) {
let command = [ let command = [
'run', 'run',
'python', 'python',
'-Wa',
'manage.py', 'manage.py',
'test', 'test',
'--settings=babybuddy.settings.test', '--settings=babybuddy.settings.test',

View File

@ -23,8 +23,8 @@ class SleepPatternTestCase(TestCase):
models.Sleep.objects.create( models.Sleep.objects.create(
child=c, child=c,
start=dt.datetime(2000, 1, 1, 0, 0, tzinfo=timezone.utc), start=dt.datetime(2000, 1, 1, 0, 0, tzinfo=dt.timezone.utc),
end=dt.datetime(2000, 1, 1, 0, 1, tzinfo=timezone.utc), end=dt.datetime(2000, 1, 1, 0, 1, tzinfo=dt.timezone.utc),
) )
sleep_pattern(models.Sleep.objects.order_by("start")) sleep_pattern(models.Sleep.objects.order_by("start"))

View File

@ -1,25 +1,25 @@
-i https://pypi.python.org/simple -i https://pypi.python.org/simple
annotated-types==0.6.0; python_version >= '3.8' annotated-types==0.6.0; python_version >= '3.8'
asgiref==3.7.2; python_version >= '3.7' asgiref==3.7.2; python_version >= '3.7'
boto3==1.33.10; python_version >= '3.7' boto3==1.34.14; python_version >= '3.8'
botocore==1.33.10; python_version >= '3.7' botocore==1.34.14; python_version >= '3.8'
defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' defusedxml==0.7.1; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'
diff-match-patch==20230430; python_version >= '3.7' diff-match-patch==20230430; python_version >= '3.7'
dj-database-url==2.1.0 dj-database-url==2.1.0
django==4.2.8; python_version >= '3.8' django==5.0.1; python_version >= '3.10'
django-appconf==1.0.6; python_version >= '3.7' django-appconf==1.0.6; python_version >= '3.7'
django-axes==6.1.1; python_version >= '3.7' django-axes==6.3.0; python_version >= '3.7'
django-dbsettings==1.3.0 django-dbsettings==1.3.0
django-filter==23.5; python_version >= '3.7' django-filter==23.5; python_version >= '3.7'
django-imagekit==5.0.0 django-imagekit==5.0.0
django-import-export==3.3.3; python_version >= '3.8' django-import-export==3.3.5; python_version >= '3.8'
django-qr-code==3.1.1; python_version >= '3.7' django-qr-code==4.0.0; python_version >= '3.7'
django-storages==1.14.2; python_version >= '3.7' django-storages==1.14.2; python_version >= '3.7'
django-taggit==5.0.1; python_version >= '3.8' django-taggit==5.0.1; python_version >= '3.8'
django-widget-tweaks==1.5.0; python_version >= '3.8' django-widget-tweaks==1.5.0; python_version >= '3.8'
djangorestframework==3.14.0; python_version >= '3.6' djangorestframework==3.14.0; python_version >= '3.6'
et-xmlfile==1.1.0; python_version >= '3.6' et-xmlfile==1.1.0; python_version >= '3.6'
faker==20.1.0; python_version >= '3.8' faker==22.0.0; python_version >= '3.8'
gunicorn==21.2.0; python_version >= '3.5' gunicorn==21.2.0; python_version >= '3.5'
jmespath==1.0.1; python_version >= '3.7' jmespath==1.0.1; python_version >= '3.7'
markuppy==1.14 markuppy==1.14
@ -27,23 +27,23 @@ odfpy==1.4.1
openpyxl==3.1.2 openpyxl==3.1.2
packaging==23.2; python_version >= '3.7' packaging==23.2; python_version >= '3.7'
pilkit==3.0 pilkit==3.0
pillow==10.1.0; python_version >= '3.8' pillow==10.2.0; python_version >= '3.8'
plotly==5.18.0; python_version >= '3.6' plotly==5.18.0; python_version >= '3.6'
psycopg2-binary==2.9.9; python_version >= '3.7' psycopg2-binary==2.9.9; python_version >= '3.7'
pydantic==2.5.2; python_version >= '3.7' pydantic==2.5.3; python_version >= '3.7'
pydantic-core==2.14.5; python_version >= '3.7' pydantic-core==2.14.6; python_version >= '3.7'
python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' python-dateutil==2.8.2; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
python-dotenv==1.0.0; python_version >= '3.8' python-dotenv==1.0.0; python_version >= '3.8'
pytz==2023.3.post1 pytz==2023.3.post1
pyyaml==6.0.1; python_version >= '3.6' pyyaml==6.0.1; python_version >= '3.6'
s3transfer==0.8.2; python_version >= '3.7' s3transfer==0.10.0; python_version >= '3.8'
segno==1.6.0; python_version >= '3.5' segno==1.6.0; python_version >= '3.5'
setuptools==69.0.2; python_version >= '3.8' setuptools==69.0.3; python_version >= '3.8'
six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3' six==1.16.0; python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'
sqlparse==0.4.4; python_version >= '3.5' sqlparse==0.4.4; python_version >= '3.5'
tablib[html,ods,xls,xlsx,yaml]==3.5.0; python_version >= '3.8' tablib[html,ods,xls,xlsx,yaml]==3.5.0; python_version >= '3.8'
tenacity==8.2.3; python_version >= '3.7' tenacity==8.2.3; python_version >= '3.7'
typing-extensions==4.8.0; python_version >= '3.8' typing-extensions==4.9.0; python_version >= '3.8'
uritemplate==4.1.1; python_version >= '3.6' uritemplate==4.1.1; python_version >= '3.6'
urllib3==2.0.7; python_version >= '3.10' urllib3==2.0.7; python_version >= '3.10'
whitenoise==6.6.0; python_version >= '3.8' whitenoise==6.6.0; python_version >= '3.8'