mirror of https://github.com/snachodog/mybuddy.git
Update to Django 5.x (#746)
This commit is contained in:
parent
9b603a9e84
commit
49d8f2b340
|
@ -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
|
||||||
|
|
2
Pipfile
2
Pipfile
|
@ -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 = "*"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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"),
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = [
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
||||||
|
|
||||||
|
|
|
@ -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 didn’t match."
|
page.context["form"],
|
||||||
|
"new_password2",
|
||||||
|
"The two password fields didn’t 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)
|
||||||
|
|
|
@ -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.",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
|
@ -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`
|
||||||
|
|
|
@ -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).
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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'
|
||||||
|
|
Loading…
Reference in New Issue