Merge remote-tracking branch 'origin/master' into tags

This commit is contained in:
Paul Konstantin Gerke 2022-03-02 22:13:41 +01:00
commit 23e7c84547
161 changed files with 11325 additions and 5767 deletions

3
.gitignore vendored
View File

@ -31,3 +31,6 @@ Pipfile.lock
# Documentation # Documentation
/site /site
# macOS files
.DS_Store

View File

@ -6,7 +6,7 @@ tasks:
gulp migrate && gulp migrate &&
gulp createcachetable gulp createcachetable
env: env:
DJANGO_SETTINGS_MODULE: babybuddy.settings.development DJANGO_SETTINGS_MODULE: babybuddy.settings.gitpod
command: gulp command: gulp
ports: ports:

View File

@ -1,5 +1,68 @@
# Changelog # Changelog
## [v1.10.0](https://github.com/babybuddy/babybuddy/tree/v1.10.0) (2022-02-21)
[Full Changelog](https://github.com/babybuddy/babybuddy/compare/v1.10.0...v1.10.1)
**Implemented enhancements:**
- Add Chinese \(simplified\) translations [\#399](https://github.com/babybuddy/babybuddy/pull/399) ([cdubz](https://github.com/cdubz))
- Use "secret" generator for `DISABLE_COLLECTSTATIC` in Heroku [\#398](https://github.com/babybuddy/babybuddy/pull/398) ([cdubz](https://github.com/cdubz))
**Fixed bugs:**
- CSRF Error \(403\) When Adding Entry \(v1.10.0\) [\#393](https://github.com/babybuddy/babybuddy/issues/393)
**Closed issues:**
- Heroku Server Error \(500\) [\#395](https://github.com/babybuddy/babybuddy/issues/395)
**Merged pull requests:**
- Additional Dutch translations [\#397](https://github.com/babybuddy/babybuddy/pull/397) ([Gitoffomalawn](https://github.com/Gitoffomalawn))
## [v1.10.0](https://github.com/babybuddy/babybuddy/tree/v1.10.0) (2022-02-16)
[Full Changelog](https://github.com/babybuddy/babybuddy/compare/v1.9.3...v1.10.0)
**Implemented enhancements:**
- Feature Request: Track baby height [\#191](https://github.com/babybuddy/babybuddy/issues/191)
- Hardcoded English strings on timeline [\#352](https://github.com/babybuddy/babybuddy/issues/352)
- breakout feeding types [\#384](https://github.com/babybuddy/babybuddy/pull/384) ([alzyee](https://github.com/alzyee))
- Rearrange dashboard cards to set timer card first [\#382](https://github.com/babybuddy/babybuddy/pull/382) ([adamaze](https://github.com/adamaze))
- Update to Django 4.x [\#378](https://github.com/babybuddy/babybuddy/pull/378) ([cdubz](https://github.com/cdubz))
- Updated Italian translation [\#376](https://github.com/babybuddy/babybuddy/pull/376) ([nos86](https://github.com/nos86))
- Fix ordering of start/stop items when start and stop times are the same [\#372](https://github.com/babybuddy/babybuddy/pull/372) ([MrApplejuice](https://github.com/MrApplejuice))
- Fix German translation mistake [\#368](https://github.com/babybuddy/babybuddy/pull/368) ([MrApplejuice](https://github.com/MrApplejuice))
- Add timer restart and stop triggers to REST-API [\#367](https://github.com/babybuddy/babybuddy/pull/367) ([MrApplejuice](https://github.com/MrApplejuice))
- Optional last name [\#361](https://github.com/babybuddy/babybuddy/pull/361) ([Alberdi](https://github.com/Alberdi))
- Add Height, Head Circumference, and BMI [\#360](https://github.com/babybuddy/babybuddy/pull/360) ([Daegalus](https://github.com/Daegalus))
- Improve iOS webapp/clip/pwa experience [\#359](https://github.com/babybuddy/babybuddy/pull/359) ([cdubz](https://github.com/cdubz))
- Convert envir variables that are supposed to be boolean to boolean [\#356](https://github.com/babybuddy/babybuddy/pull/356) ([MagiX13](https://github.com/MagiX13))
**Fixed bugs:**
- iOS 15 web app experience is degraded [\#357](https://github.com/babybuddy/babybuddy/issues/357)
- Boolean environmental variables [\#354](https://github.com/babybuddy/babybuddy/issues/354)
- Sleep graph has incorrect positioning when there is a gap in days. [\#286](https://github.com/babybuddy/babybuddy/issues/286)
- Sleep graph issues [\#283](https://github.com/babybuddy/babybuddy/issues/283)
- KeyError at /children/XXX/reports/sleep/pattern/ [\#211](https://github.com/babybuddy/babybuddy/issues/211)
- fix\(sleep-reports\): \#286 Init all days in the period to remove gaps [\#341](https://github.com/babybuddy/babybuddy/pull/341) ([codisart](https://github.com/codisart))
**Closed issues:**
- Show type breakdown on feeding\_amounts report [\#383](https://github.com/babybuddy/babybuddy/issues/383)
- Error during add/edit action for fed and diaper [\#374](https://github.com/babybuddy/babybuddy/issues/374)
- Evaluate replacements for Easy Thumbnails [\#373](https://github.com/babybuddy/babybuddy/issues/373)
- Issue with timer API for Feeding, Sleep, and Tummy-Time [\#363](https://github.com/babybuddy/babybuddy/issues/363)
**Merged pull requests:**
- Hide feeding\_day card when no information present [\#386](https://github.com/babybuddy/babybuddy/pull/386) ([BenjaminHae](https://github.com/BenjaminHae))
- add HA Addon link [\#375](https://github.com/babybuddy/babybuddy/pull/375) ([OttPeterR](https://github.com/OttPeterR))
- Minor changes to run into subdir [\#358](https://github.com/babybuddy/babybuddy/pull/358) ([MagiX13](https://github.com/MagiX13))
## [v1.9.3](https://github.com/babybuddy/babybuddy/tree/v1.9.3) (2021-12-14) ## [v1.9.3](https://github.com/babybuddy/babybuddy/tree/v1.9.3) (2021-12-14)
[Full Changelog](https://github.com/babybuddy/babybuddy/compare/v1.9.2...v1.9.3) [Full Changelog](https://github.com/babybuddy/babybuddy/compare/v1.9.2...v1.9.3)

View File

@ -44,6 +44,8 @@ for information about how to create/update translations.
### Available languages ### Available languages
:cn: Chinese (simplified)
:netherlands: Dutch :netherlands: Dutch
:uk: English (U.K.) :uk: English (U.K.)

View File

@ -12,13 +12,17 @@
"self-host" "self-host"
], ],
"repository": "https://github.com/babybuddy/babybuddy", "repository": "https://github.com/babybuddy/babybuddy",
"website": "http://www.baby-buddy.net", "website": "https://docs.baby-buddy.net",
"buildpacks": [ "buildpacks": [
{ {
"url": "heroku/python" "url": "heroku/python"
} }
], ],
"env": { "env": {
"DISABLE_COLLECTSTATIC": {
"description": "Prevent static asset collection.",
"generator": "secret"
},
"DJANGO_SETTINGS_MODULE": { "DJANGO_SETTINGS_MODULE": {
"description": "A prebuilt configuration for Heroku.", "description": "A prebuilt configuration for Heroku.",
"value": "babybuddy.settings.heroku" "value": "babybuddy.settings.heroku"
@ -26,16 +30,15 @@
"SECRET_KEY": { "SECRET_KEY": {
"description": "Used for the auth system.", "description": "Used for the auth system.",
"generator": "secret" "generator": "secret"
},
"DISABLE_COLLECTSTATIC": {
"description": "Prevent static asset collection.",
"value": "1"
},
"TIME_ZONE": {
"description": "Sets the instance default time zone.",
"value": "UTC"
} }
}, },
"environments": {
"review": {
"scripts": {
"postdeploy": "python manage.py migrate && python manage.py createcachetable && python manage.py reset --no-input"
}
}
},
"scripts": { "scripts": {
"postdeploy": "python manage.py migrate && python manage.py createcachetable" "postdeploy": "python manage.py migrate && python manage.py createcachetable"
} }

View File

@ -46,7 +46,7 @@
""" # noqa """ # noqa
__title__ = "Baby Buddy" __title__ = "Baby Buddy"
__version__ = "1.9.3" __version__ = "1.10.1"
__license__ = "BSD 2-Clause" __license__ = "BSD 2-Clause"
VERSION = __version__ VERSION = __version__

View File

@ -3,6 +3,7 @@
!base.py !base.py
!ci.py !ci.py
!development.py !development.py
!gitpod.py
!heroku.py !heroku.py
!production.example.py !production.example.py
!test.py !test.py

View File

@ -20,7 +20,7 @@ DEBUG = bool(strtobool(os.environ.get("DEBUG") or "False"))
# Applications # Applications
# https://docs.djangoproject.com/en/3.0/ref/applications/ # https://docs.djangoproject.com/en/4.0/ref/applications/
INSTALLED_APPS = [ INSTALLED_APPS = [
"api", "api",
@ -47,7 +47,7 @@ INSTALLED_APPS = [
] ]
# Middleware # Middleware
# https://docs.djangoproject.com/en/3.0/ref/middleware/ # https://docs.djangoproject.com/en/4.0/ref/middleware/
MIDDLEWARE = [ MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware", "django.middleware.security.SecurityMiddleware",
@ -67,18 +67,18 @@ MIDDLEWARE = [
# URL dispatcher # URL dispatcher
# https://docs.djangoproject.com/en/3.0/topics/http/urls/ # https://docs.djangoproject.com/en/4.0/topics/http/urls/
ROOT_URLCONF = "babybuddy.urls" ROOT_URLCONF = "babybuddy.urls"
# Templates # Templates
# https://docs.djangoproject.com/en/3.0/ref/settings/#std:setting-TEMPLATES # https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-TEMPLATES
TEMPLATES = [ TEMPLATES = [
{ {
"BACKEND": "django.template.backends.django.DjangoTemplates", "BACKEND": "django.template.backends.django.DjangoTemplates",
"DIRS": [], "DIRS": ["babybuddy/templates/error"],
"APP_DIRS": True, "APP_DIRS": True,
"OPTIONS": { "OPTIONS": {
"context_processors": [ "context_processors": [
@ -93,7 +93,7 @@ TEMPLATES = [
# Database # Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases # https://docs.djangoproject.com/en/4.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",
@ -114,7 +114,7 @@ DATABASES = {"default": config}
# Cache # Cache
# https://docs.djangoproject.com/en/3.0/topics/cache/ # https://docs.djangoproject.com/en/4.0/topics/cache/
CACHES = { CACHES = {
"default": { "default": {
@ -125,13 +125,13 @@ CACHES = {
# WGSI # WGSI
# https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ # https://docs.djangoproject.com/en/4.0/howto/deployment/wsgi/
WSGI_APPLICATION = "babybuddy.wsgi.application" WSGI_APPLICATION = "babybuddy.wsgi.application"
# Authentication # Authentication
# https://docs.djangoproject.com/en/3.0/topics/auth/default/ # https://docs.djangoproject.com/en/4.0/topics/auth/default/
AUTHENTICATION_BACKENDS = [ AUTHENTICATION_BACKENDS = [
"axes.backends.AxesBackend", "axes.backends.AxesBackend",
@ -146,14 +146,14 @@ LOGOUT_REDIRECT_URL = "babybuddy:login"
# Timezone # Timezone
# https://docs.djangoproject.com/en/3.0/topics/i18n/timezones/ # https://docs.djangoproject.com/en/4.0/topics/i18n/timezones/
USE_TZ = True USE_TZ = True
TIME_ZONE = os.environ.get("TIME_ZONE") or "UTC" TIME_ZONE = os.environ.get("TIME_ZONE") or "UTC"
# Internationalization # Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/ # https://docs.djangoproject.com/en/4.0/topics/i18n/
USE_I18N = True USE_I18N = True
@ -164,9 +164,10 @@ LOCALE_PATHS = [
] ]
LANGUAGES = [ LANGUAGES = [
("zh-hans", _("Chinese (simplified)")),
("nl", _("Dutch")),
("en-US", _("English (US)")), ("en-US", _("English (US)")),
("en-GB", _("English (UK)")), ("en-GB", _("English (UK)")),
("nl", _("Dutch")),
("fr", _("French")), ("fr", _("French")),
("fi", _("Finnish")), ("fi", _("Finnish")),
("de", _("German")), ("de", _("German")),
@ -180,7 +181,7 @@ LANGUAGES = [
# Format localization # Format localization
# https://docs.djangoproject.com/en/3.0/topics/i18n/formatting/ # https://docs.djangoproject.com/en/4.0/topics/i18n/formatting/
USE_L10N = True USE_L10N = True
@ -196,7 +197,7 @@ USE_24_HOUR_TIME_FORMAT = bool(
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/ # https://docs.djangoproject.com/en/4.0/howto/static-files/
# http://whitenoise.evans.io/en/stable/django.html # http://whitenoise.evans.io/en/stable/django.html
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
@ -206,7 +207,7 @@ STATICFILES_FINDERS = [
"django.contrib.staticfiles.finders.AppDirectoriesFinder", "django.contrib.staticfiles.finders.AppDirectoriesFinder",
] ]
STATIC_URL = "static/" STATIC_URL = os.path.join(os.environ.get("SUB_PATH") or "", "static/")
STATIC_ROOT = os.path.join(BASE_DIR, "static") STATIC_ROOT = os.path.join(BASE_DIR, "static")
@ -214,7 +215,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/3.0/topics/files/ # https://docs.djangoproject.com/en/4.0/topics/files/
MEDIA_ROOT = os.path.join(BASE_DIR, "media") MEDIA_ROOT = os.path.join(BASE_DIR, "media")
@ -232,19 +233,24 @@ if AWS_STORAGE_BUCKET_NAME:
# Security # Security
# https://docs.djangoproject.com/en/3.2/ref/settings/#secure-proxy-ssl-header # https://docs.djangoproject.com/en/4.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/3.2/topics/http/sessions/#settings # https://docs.djangoproject.com/en/4.0/topics/http/sessions/#settings
SESSION_COOKIE_HTTPONLY = True SESSION_COOKIE_HTTPONLY = True
# SESSION_COOKIE_SECURE = True # SESSION_COOKIE_SECURE = True
# https://docs.djangoproject.com/en/3.2/ref/csrf/#settings # https://docs.djangoproject.com/en/4.0/ref/csrf/#settings
CSRF_COOKIE_HTTPONLY = True CSRF_COOKIE_HTTPONLY = True
# CSRF_COOKIE_SECURE = True # CSRF_COOKIE_SECURE = True
CSRF_FAILURE_VIEW = "babybuddy.views.csrf_failure"
CSRF_TRUSTED_ORIGINS = list(
filter(None, os.environ.get("CSRF_TRUSTED_ORIGINS", "").split(","))
)
# https://docs.djangoproject.com/en/3.2/topics/auth/passwords/
# https://docs.djangoproject.com/en/4.0/topics/auth/passwords/
AUTH_PASSWORD_VALIDATORS = [ AUTH_PASSWORD_VALIDATORS = [
{ {
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator", "NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
@ -301,12 +307,12 @@ AXES_FAILURE_LIMIT = 5
# Session configuration # Session configuration
# Used by RollingSessionMiddleware to determine how often to reset the session. # Used by RollingSessionMiddleware to determine how often to reset the session.
# See https://docs.djangoproject.com/en/3.0/topics/http/sessions/ # See https://docs.djangoproject.com/en/4.0/topics/http/sessions/
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/3.2/releases/3.2/#customizing-type-of-auto-created-primary-keys # See https://docs.djangoproject.com/en/4.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,6 @@ SECRET_KEY = "CISECRETKEYIGUESS"
# Static files (CSS, JavaScript, Images) # Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/3.0/howto/static-files/ # https://docs.djangoproject.com/en/4.0/howto/static-files/
STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage" STATICFILES_STORAGE = "django.contrib.staticfiles.storage.StaticFilesStorage"

View File

@ -1,14 +1,14 @@
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/3.0/howto/deployment/checklist/ # https://docs.djangoproject.com/en/4.0/howto/deployment/checklist/
SECRET_KEY = "CHANGE ME" SECRET_KEY = os.environ.get("SECRET_KEY") or "DEVELOPMENT!!"
DEBUG = 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/3.0/howto/static-files/ # https://docs.djangoproject.com/en/4.0/howto/static-files/
# #
# Comment out STATICFILES_STORAGE and uncomment DEBUG = False to test with # Comment out STATICFILES_STORAGE and uncomment DEBUG = False to test with
# production static files. # production static files.

View File

@ -0,0 +1,9 @@
from .development import *
# CSRF configuration
# https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-CSRF_TRUSTED_ORIGINS
# https://www.gitpod.io/docs/environment-variables/#default-environment-variables
CSRF_TRUSTED_ORIGINS = [
os.environ.get("GITPOD_WORKSPACE_URL").replace("https://", "https://8000-")
]

View File

@ -11,13 +11,13 @@ BABY_BUDDY["ALLOW_UPLOADS"] = bool(
# Database # Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases # https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = {"default": dj_database_url.config(conn_max_age=500)} DATABASES = {"default": dj_database_url.config(conn_max_age=500)}
# Email # Email
# https://docs.djangoproject.com/en/3.0/topics/email/ # https://docs.djangoproject.com/en/4.0/topics/email/
# https://devcenter.heroku.com/articles/sendgrid#python # https://devcenter.heroku.com/articles/sendgrid#python
SENDGRID_USERNAME = os.environ.get("SENDGRID_USERNAME", None) # noqa: F405 SENDGRID_USERNAME = os.environ.get("SENDGRID_USERNAME", None) # noqa: F405

View File

@ -8,7 +8,7 @@ SECRET_KEY = ""
ALLOWED_HOSTS = [""] ALLOWED_HOSTS = [""]
# Database # Database
# https://docs.djangoproject.com/en/3.0/ref/settings/#databases # https://docs.djangoproject.com/en/4.0/ref/settings/#databases
DATABASES = { DATABASES = {
"default": { "default": {
@ -18,7 +18,7 @@ DATABASES = {
} }
# Media files # Media files
# https://docs.djangoproject.com/en/3.0/topics/files/ # https://docs.djangoproject.com/en/4.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/3.2/topics/http/sessions/#settings # See https://docs.djangoproject.com/en/4.0/topics/http/sessions/#settings
# See https://docs.djangoproject.com/en/3.2/ref/csrf/#settings # See https://docs.djangoproject.com/en/4.0/ref/csrf/#settings
# SESSION_COOKIE_SECURE = True # SESSION_COOKIE_SECURE = True
# CSRF_COOKIE_SECURE = True # CSRF_COOKIE_SECURE = True

View File

@ -3,8 +3,8 @@ from .base import *
SECRET_KEY = "TESTS" SECRET_KEY = "TESTS"
# Password hasher configuration # Password hasher configuration
# See https://docs.djangoproject.com/en/3.2/ref/settings/#password-hashers # See https://docs.djangoproject.com/en/4.0/ref/settings/#password-hashers
# See https://docs.djangoproject.com/en/3.2/topics/testing/overview/#password-hashing # See https://docs.djangoproject.com/en/4.0/topics/testing/overview/#password-hashing
PASSWORD_HASHERS = [ PASSWORD_HASHERS = [
"django.contrib.auth.hashers.MD5PasswordHasher", "django.contrib.auth.hashers.MD5PasswordHasher",

View File

@ -29,6 +29,7 @@ BabyBuddy.DatetimePicker = function ($, moment) {
var defaultOptions = { var defaultOptions = {
buttons: { showToday: true, showClose: true }, buttons: { showToday: true, showClose: true },
defaultDate: 'now', defaultDate: 'now',
focusOnShow: false,
format: 'L LT', format: 'L LT',
ignoreReadonly: true, ignoreReadonly: true,
locale: moment.locale(), locale: moment.locale(),

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 26.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 334.8 305.4" style="enable-background:new 0 0 334.8 305.4;" xml:space="preserve">
<style type="text/css">
.st0{fill:#37ABE9;}
</style>
<g transform="matrix(1, 0, 0, 1, -910.945007, -2957.187256)">
<path class="st0" d="M1036.6,3076.7c0,6.4-3.8,11.5-8.4,11.5c-4.6,0-8.4-5.2-8.4-11.5c0-6.4,3.8-11.5,8.4-11.5
C1032.8,3065.2,1036.6,3070.4,1036.6,3076.7z"/>
<path class="st0" d="M1136.8,3076.7c0,6.4-3.8,11.5-8.4,11.5c-4.6,0-8.4-5.2-8.4-11.5c0-6.4,3.8-11.5,8.4-11.5
C1133.1,3065.2,1136.8,3070.4,1136.8,3076.7z"/>
</g>
<path class="st0" d="M305.9,138c-1.1,0-2.9,0.4-3.3-1.1c-10.7-49.5-48-87.6-95.1-103.1c-5-25.3-34.8-47.7-55.8-23.9
c-14.2,15.3,5.3,42.2,20.6,25.9c3.3-4.6,0-11.9-6-11.9c-3.3,0-4.6,3.3-6,5.3c-2.7,3.3-5.3-2.7-5.3-5.3c0-3.3,0.7-7.3,3.3-10
c27.3-21.4,48.7,24.4,17.2,37.8c-15.3,6-31.2-3.3-39.5-15.4c-2-3-3.3-3.2-4.4-2.9C82.3,45.5,43,86.9,32.1,136.9
c-0.3,1.5-2.1,1.1-3.3,1.1c-38.9,0.8-37.4,59.2,1.7,57.7C47,256.5,98.9,306,167.4,305.4c68.4,0.6,120.4-48.9,136.8-109.7
C343.1,197.2,345,138.9,305.9,138z M217.5,87.5c30.3,0,30.3,64,0,64C187.1,151.5,187.1,87.6,217.5,87.5z M107.2,185.9
c-1.2,11.6-19.1,11.3-19.9-0.4c-0.4-4.4,1.2-8,3.5-12.8c2.8-5.7,4.6-10.9,6-16.7C93.8,171.1,107.6,174.7,107.2,185.9z M94,119.5
c0.6-42.1,45.9-42.1,46.5,0C139.9,161.6,94.6,161.6,94,119.5z M202.7,254.2l-71.5,0.2c-0.9,0-1.5-0.7-1.5-1.6
c1.9-44.8,72.9-45.8,74.5-0.2C204.2,253.4,203.5,254.2,202.7,254.2z M254.5,190.8c-0.7,1.2-0.6,8.2-3.2,7.4c0,0-69.3-15.9-69.3-15.9
c-0.8-0.3-6,3.8-5.8,4.8c0,0,0.6,13.2,0.6,13.2c0,0.9-0.6,1.6-1.5,1.6h-16.6c-0.9,0-1.5-0.7-1.5-1.6c0,0,0.6-13.2,0.6-13.2
c-15.7-14.9,0.2-10.4-22.6-15.5c-0.8-0.2-1.3-1-1.1-1.9l4.4-16c0.2-0.8,1.1-1.3,1.9-1l12.3,4c0.4,0.6,6.9-3.9,6.5-4.6
c0,0,3-69.7,3-69.7c0-0.8,0.7-1.4,1.5-1.4c1.4,0.4,8-1.1,8,1.4c0.8,1.3,1.6,70.5,3.8,71c5,2.6,8.6,7.3,9.8,12.9
c-0.1,2.3,67.2,21.7,68.2,22.8C254.3,189.3,254.7,190.1,254.5,190.8z"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

@ -0,0 +1,8 @@
{% extends 'error/base.html' %}
{% load i18n %}
{% block title %}400 {% trans "Bad Request" %}{% endblock %}
{% block content %}
<h1>400 {% trans "Bad Request" %}</h1>
{% endblock %}

View File

@ -1,13 +1,10 @@
{% extends 'babybuddy/page.html' %} {% extends 'error/base.html' %}
{% load i18n widget_tweaks %} {% load i18n %}
{% block title %}403 {% trans "Permission Denied" %}{% endblock %} {% block title %}403 {% trans "Permission Denied" %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item active" aria-current="page">{% trans "Permission Denied" %}</li>
{% endblock %}
{% block content %} {% block content %}
<h1>403 {% trans "Permission Denied" %}</h1>
<div class="alert alert-danger" role="alert"> <div class="alert alert-danger" role="alert">
{% blocktrans trimmed %} {% blocktrans trimmed %}
You do not have permission to access this resource. Contact a site You do not have permission to access this resource. Contact a site

View File

@ -0,0 +1,21 @@
{% extends 'error/base.html' %}
{% load i18n %}
{% block title %}403 {{ title }}{% endblock %}
{% block breadcrumb_nav %}{% endblock %}
{% block content %}
<h1>403: {{ title }}</h1>
<div class="alert alert-danger" role="alert">
{{ main }} {{ reason }}
</div>
<div class="jumbotron">
<h2>{% trans "How to Fix" %}</h2>
{% blocktrans trimmed with origin=origin %}
Add <samp>{{ origin }}</samp> to the <code>CSRF_TRUSTED_ORIGINS</code>
environment variable. If multiple origins are required separate
with commas.
{% endblocktrans %}
</div>
{% endblock %}

View File

@ -0,0 +1,13 @@
{% extends 'error/base.html' %}
{% load i18n %}
{% block title %}404 {% trans "Page Not Found" %}{% endblock %}
{% block content %}
<h1>404 {% trans "Page Not Found" %}</h1>
<div>
{% blocktrans trimmed with request_path=request_path %}
The path <code>{{ request_path }}</code> does not exist.
{% endblocktrans %}
</div>
{% endblock %}

View File

@ -0,0 +1,8 @@
{% extends 'error/base.html' %}
{% load i18n %}
{% block title %}500 {% trans "Server Error" %}{% endblock %}
{% block content %}
<h1>500 {% trans "Server Error" %}</h1>
{% endblock %}

View File

@ -0,0 +1,20 @@
{% extends 'babybuddy/base.html' %}
{% load i18n static %}
{% block breadcrumb_nav %}{% endblock %}
{% block page %}
<div class="container mt-2 mt-lg-4">
<div class="row justify-content-md-center">
<div class="col-lg-12 mb-4">
<div class="d-sm-flex">
<img class="d-inline-block align-self-top mr-2 mt-2 text-center" src="{% static "babybuddy/logo/logo-sad.png" %}" width="65" height="65">
<div class="p-2 flex-grow-1">
{% block content %}{% endblock %}
<a href="{% url "babybuddy:root-router" %}" class="btn btn-outline-primary mt-3">{% trans "Return to Baby Buddy" %}</a>
</div>
</div>
</div>
</div>
</div>
{% endblock %}

View File

@ -186,3 +186,21 @@ class FormsTestCase(TestCase):
self.assertEqual( self.assertEqual(
self.user.settings.dashboard_hide_age, datetime.timedelta(days=1) self.user.settings.dashboard_hide_age, datetime.timedelta(days=1)
) )
def test_csrf_error_handling(self):
c = HttpClient(enforce_csrf_checks=True)
c.login(**self.credentials)
# Add a CSRF cookie to the client by making a request with the logout form.
c.get("/", follow=True)
# Send POST request with an invalid Origin.
headers = {"HTTP_ORIGIN": "https://www.example.com"}
data = {"csrfmiddlewaretoken": c.cookies["csrftoken"].value}
response = c.post("/logout/", data=data, follow=True, **headers)
# Assert response contains Baby Buddy's custom 403 handler text.
self.assertContains(response, "How to Fix", status_code=403)
response = c.post("/logout/", data=data, follow=True)
self.assertEqual(response.status_code, 200)

View File

@ -5,12 +5,16 @@ from django.contrib.auth.forms import PasswordChangeForm
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.contrib.auth.views import LogoutView as LogoutViewBase from django.contrib.auth.views import LogoutView as LogoutViewBase
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.http import HttpResponseForbidden
from django.middleware.csrf import REASON_BAD_ORIGIN
from django.shortcuts import redirect, render from django.shortcuts import redirect, render
from django.template import loader
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
from django.utils import translation from django.utils import translation
from django.utils.decorators import method_decorator from django.utils.decorators import method_decorator
from django.utils.text import format_lazy from django.utils.text import format_lazy
from django.utils.translation import gettext as _, gettext_lazy from django.utils.translation import gettext as _, gettext_lazy
from django.views import csrf
from django.views.decorators.cache import never_cache from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_POST from django.views.decorators.http import require_POST
@ -25,6 +29,28 @@ from babybuddy import forms
from babybuddy.mixins import LoginRequiredMixin, PermissionRequiredMixin, StaffOnlyMixin from babybuddy.mixins import LoginRequiredMixin, PermissionRequiredMixin, StaffOnlyMixin
def csrf_failure(request, reason=""):
"""
Overrides the 403 CSRF failure template for bad origins in order to provide more
userful information about how to resolve the issue.
"""
if (
"HTTP_ORIGIN" in request.META
and reason == REASON_BAD_ORIGIN % request.META["HTTP_ORIGIN"]
):
context = {
"title": _("Forbidden"),
"main": _("CSRF verification failed. Request aborted."),
"reason": reason,
"origin": request.META["HTTP_ORIGIN"],
}
template = loader.get_template("error/403_csrf_bad_origin.html")
return HttpResponseForbidden(template.render(context), content_type="text/html")
return csrf.csrf_failure(request, reason, "403_csrf.html")
class RootRouter(LoginRequiredMixin, RedirectView): class RootRouter(LoginRequiredMixin, RedirectView):
""" """
Redirects to the site dashboard. Redirects to the site dashboard.

View File

@ -9,32 +9,11 @@
<a href="{% url 'core:child' object.slug %}" class="btn" title="{% trans "Timeline" %}"> <a href="{% url 'core:child' object.slug %}" class="btn" title="{% trans "Timeline" %}">
<i class="icon-timeline" aria-hidden="true"></i> <i class="icon-timeline" aria-hidden="true"></i>
</a> </a>
<a href="{% url 'reports:report-list' object.slug %}" class="btn" title="{% trans "Reports" %}">
<i class="icon-graph" aria-hidden="true"></i>
</a>
{% endif %} {% endif %}
<div class="btn-group" role="group">
<button id="reports-dropdown"
class="btn dropdown-toggle"
title="{% trans "Reports" %}"
type="button"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"><i class="icon-graph" aria-hidden="true"></i></button>
<div class="dropdown-menu" aria-labelledby="reports-dropdown">
<a class="dropdown-item" href="{% url 'reports:report-diaperchange-amounts-child' object.slug %}">{% trans "Diaper Change Amounts" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-diaperchange-types-child' object.slug %}">{% trans "Diaper Change Types" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-diaperchange-lifetimes-child' object.slug %}">{% trans "Diaper Lifetimes" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-feeding-amounts-child' object.slug %}">{% trans "Feeding Amounts" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-feeding-duration-child' object.slug %}">{% trans "Feeding Durations (Average)" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-sleep-pattern-child' object.slug %}">{% trans "Sleep Pattern" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-sleep-totals-child' object.slug %}">{% trans "Sleep Totals" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-tummytime-duration-child' object.slug %}">{% trans "Tummy Time Durations (Sum)" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-weight-weight-child' object.slug %}">{% trans "Weight" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-height-height-child' object.slug %}">{% trans "Height" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-head-circumference-head-circumference-child' object.slug %}">{% trans "Head Circumference" %}</a>
<a class="dropdown-item" href="{% url 'reports:report-bmi-bmi-child' object.slug %}">{% trans "BMI" %}</a>
</div>
</div>
{% if perms.core.change_child %} {% if perms.core.change_child %}
<a class="btn d-none d-md-inline-block" <a class="btn d-none d-md-inline-block"
href="{% url 'core:child-update' object.slug %}" href="{% url 'core:child-update' object.slug %}"

View File

@ -44,9 +44,9 @@ If no `Authorization` header set, or the key is not valid the API will return
## Schema ## Schema
The API schema in [OpenAPI format](https://swagger.io/specification/) can be The API schema in [OpenAPI format](https://swagger.io/specification/) can be
found in the [`openapi-schema.yml`](/openapi-schema.yml) file in the project found in the [`openapi-schema.yml`](https://github.com/babybuddy/babybuddy/tree/master/openapi-schema.yml)
root. A live version is also available at the `/api/schema` path of a running file in the project root. A live version is also available at the `/api/schema` path of
instance. a running instance.
## `GET` Method ## `GET` Method

View File

@ -1,325 +0,0 @@
# Contributing
Baby Buddy's maintainers accept and encourage contributions via GitHub [Issues](https://github.com/babybuddy/babybuddy/issues)
and [Pull Requests](https://github.com/babybuddy/babybuddy/pulls). Maintainers
and users may also be found at [babybuddy/Lobby](https://gitter.im/babybuddy/Lobby)
on Gitter.
## Pull request process
1. Fork this repository and commit your changes.
1. Make and commit tests for any new features or major functionality changes.
1. Run `gulp lint` and `gulp test` (see [Gulp Commands](#gulp-commands)) and
ensure that all tests pass.
1. Updated static assets if necessary and commit the `/static` folder (see [`gulp updatestatic`](#updatestatic)).
1. Open a [new pull request](https://github.com/babybuddy/babybuddy/compare) against
the `master` branch.
Maintainers will review new pull requests will as soon as possible, and we will
do our best to provide feedback and support potential contributors.
## Translation
### POEditor
Baby Buddy uses [POEditor](https://poeditor.com/) for translation contributions.
Interested contributors can [join translation of Baby Buddy](https://poeditor.com/join/project/QwQqrpTIzn)
for access to a simple, web-based frontend for adding/editing translation files
to the project.
### Manual
Baby Buddy has support for translation/localization. A manual translation
process will look something like this:
1. Set up a development environment (see [Development](#development) below).
1. Run `gulp makemessages -l xx` where `xx` is a specific locale code in the
[ISO 639-1 format](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g.,
"il" for Italian or "es" for Spanish). This creates a new translation file at
`locale/xx/LC_MESSAGES/django.po`, or updates one if it exists.
1. Open the created/updated `django.po` file and update the header template
with license and contact info.
1. Start translating! Each translatable string will have a `msgid` value with
the string in English and a corresponding (empty) `msgstr` value where a
translated string can be filled in.
1. Once all strings have been translated, run `gulp compilemessages -l xx` to
compile an optimized translation file (`locale/xx/LC_MESSAGES/django.mo`).
1. To expose the new translation as a user setting, add the locale code to the
`LANGUAGES` array in the base settings file (`babybuddy/settings/base.py`).
1. Check if Plotly offers a translation (in `node_modules/plotly.js/dist/`) for
the language. If it does:
1. Add the Plotly translation file path to [`gulpfile.config.js`](/gulpfile.config.js) in
`scriptsConfig.graph`.
1. Build, collect, and commit the `/static` folder (see
[`gulp updatestatic`](#updatestatic)).
1. Check if Moment offers a translation (in `node_modules/moment/locale/`) for
the language. If it does:
1. Add the Moment translation file path to [`gulpfile.config.js`](/gulpfile.config.js) in
`scriptsConfig.vendor`.
1. Build, collect, and commit the `/static` folder (see
[`gulp updatestatic`](#updatestatic)).
1. Run the development server, log in, and update the user language to test the
newly translated strings.
Once the translation is complete, commit the new files and changes to a fork
and [create a pull request](#pull-request-process) for review.
For more information on the Django translation process, see Django's
documentation section: [Translation](https://docs.djangoproject.com/en/3.0/topics/i18n/translation/).
## Development
### Requirements
- Python 3.6+, pip, pipenv
- NVM (NodeJS 14.x and NPM 7.x)
- Gulp
- Possibly `libpq-dev`
- This is necessary if `psycopg2` can't find an appropriate prebuilt binary.
### Installation
1. Install pipenv per [Installing pipenv](https://pipenv.pypa.io/en/latest/install/#installing-pipenv)
1. Install required Python packages, including dev packages
pipenv install --three --dev
- If this fails, install `libpq-dev` (e.g. `sudo apt install libpq-dev`) and try again.
1. Installed Node 14.x (if necessary)
nvm install 14
1. Activate Node 14.x
nvm use
1. Install Gulp CLI
npm install -g gulp-cli
1. Install required Node packages
npm install
1. Set, at least, the `DJANGO_SETTINGS_MODULE` environment variable
export DJANGO_SETTINGS_MODULE=babybuddy.settings.development
This process will differ based on the host OS. The above example is for
Linux-based systems. See [Configuration](/docs/setup/configuration.md) for other
settings and methods for defining them.
1. Migrate the database
gulp migrate
1. Create cache table
gulp createcachetable
1. Build assets and run the server
gulp
This command will also watch for file system changes to rebuild assets and
restart the server as needed.
Open [http://127.0.0.1:8000](http://127.0.0.1:8000) and log in with the default
username and password (`admin`/`admin`).
### Gulp commands
[`gulpfile.js`](/gulpfile.js) defines Baby Buddy's Gulp commands.
[`babybuddy/management/commands`](/babybuddy/management/commands) defines Baby Buddy's
management commands.
- [`gulp`](#gulp)
- [`gulp build`](#build)
- [`gulp clean`](#clean)
- [`gulp collectstatic`](#collectstatic)
- [`gulp compilemessages`](#compilemessages)
- [`gulp coverage`](#coverage)
- [`gulp createcachetable`](#createcachetable)
- [`gulp docs:build`](#docsbuild)
- [`gulp docs:deploy`](#docsdeploy)
- [`gulp docs:watch`](#docswatch)
- [`gulp extras`](#extras)
- [`gulp fake`](#fake)
- [`gulp generatescheme`](#generatescheme)
- [`gulp lint`](#lint)
- [`gulp makemessages`](#makemessages)
- [`gulp makemigrations`](#makemigrations)
- [`gulp migrate`](#migrate)
- [`gulp reset`](#reset)
- [`gulp runserver`](#runserver)
- [`gulp scripts`](#scripts)
- [`gulp styles`](#styles)
- [`gulp test`](#test)
- [`gulp updatestatic`](#updatestatic)
#### `gulp`
Executes the `build` and `watch` commands and runs Django's development server.
This command also accepts the special parameter `--ip` for setting the
server IP address. See [`gulp runserver`](#runserver) for details.
#### `build`
Creates all script, style and "extra" assets and places them in the
`babybuddy/static` folder.
#### `docs:build`
Builds the documentation site in a local directory (`site` by default).
#### `docs:deploy`
Deploys the documentation site to GitHub Pages.
#### `docs:watch`
Runs a local server for the documentation site reloading whenever documentation
sites files (in the `docs` directory) as modified.
#### `clean`
Deletes all build folders and the root `static` folder. Generally this should
be run before a `gulp build` to remove previous build files and the generated
static assets.
#### `collectstatic`
Executes Django's `collectstatic` management task. This task relies on files in
the `babybuddy/static` folder, so generally `gulp build` should be run before
this command for production deployments. Gulp also passes along
non-overlapping arguments for this command, e.g. `--no-input`.
Note: a `SECRET_KEY` value must be set for this command to work.
#### `compilemessages`
Executes Django's `compilemessages` management task. See [django-admin compilemessages](https://docs.djangoproject.com/en/3.0/ref/django-admin/#compilemessages)
for more details about other options and functionality of this command.
#### `coverage`
Create a test coverage report. See [`.coveragerc`](/.coveragerc) for default
settings information.
#### `createcachetable`
Executes Django's `createcachetable` management task. See [django-admin createcachetable](https://docs.djangoproject.com/en/3.0/ref/django-admin/#createcachetable)
for more details about other options and functionality of this command.
#### `extras`
Copies "extra" files (fonts, images and server root contents) to the build
folder.
#### `fake`
Adds some fake data to the database. By default, ``fake`` creates one child and
31 days of random data. Use the `--children` and `--days` flags to change the
default values, e.g. `gulp fake --children 5 --days 7` to generate five fake
children and seven days of data for each.
#### `generateschema`
Updates the [`openapi-schema.yml`](/openapi-schema.yml) file in the project root
based on current API functionality.
#### `lint`
Executes Python and SASS linting for all relevant source files.
#### `makemessages`
Executes Django's `makemessages` management task. See [django-admin makemessages](https://docs.djangoproject.com/en/3.0/ref/django-admin/#makemessages)
for more details about other options and functionality of this command. When
working on a single translation, the `-l` flag is useful to make message for
only that language, e.g. `gulp makemessages -l fr` to make only a French
language translation file.
#### `makemigrations`
Executes Django's `makemigrations` management task. Gulp also passes along
non-overlapping arguments for this command.
#### `migrate`
Executes Django's `migrate` management task. In addition to migrating the
database, this command creates the default `admin` user. Gulp also passes along
non-overlapping arguments for this command.
#### `reset`
Resets the database to a default state *with* one fake child and 31 days of
fake data.
#### `runserver`
Executes Django's `runserver` management task. Gulp passes non-overlapping
arguments for this command.
A special parameter, `--ip`, is also available to set the IP for the server.
E.g., execute `gulp runserver --ip 0.0.0.0:8000` to make the server available to
other devices on your local network.
#### `scripts`
Builds and combines relevant application scripts. Generally this command does
not need to be executed independently - see the `build` command.
#### `styles`
Builds and combines SASS styles in to CSS files. Generally this command does
not need to be executed independently - see the `build` command.
#### `test`
Executes Baby Buddy's suite of tests.
Gulp also passes along non-overlapping arguments for this command, however
individual tests cannot be run with this command. `python manage.py test` can be
used for individual test execution.
#### `updateglyphs`
Downloads generated glyph font files data from [Fonttello](https://fontello.com/)
based on [`config.json` file](/babybuddy/static_src/fontello/config.json). This
only needs to be run after making changes to the config file.
#### `updatestatic`
Rebuilds Baby Buddy's `/static` folder by running the following commands in
order:
- [`lint`](#lint)
- [`clean`](#clean)
- [`build`](#build)
- [`collectstatic`](#collectstatic)
Execute and commit changes made by this command whenever changing Baby Buddy's
frontend code (SASS, JS, etc.).
### Icon Font
Baby Buddy uses [Fontello](https://fontello.com/) to build a custom icon font
for icons used throughout the application. See [`babybuddy/static_src/fontello`](/babybuddy/static_src/fontello)
for further documentation about using the config file with Fontello and license
information for fonts used by this application.

View File

@ -0,0 +1,66 @@
# Development environment
[![Gitpod ready-to-code](https://img.shields.io/badge/Gitpod-ready--to--code-blue?logo=gitpod)](https://gitpod.io/#https://github.com/babybuddy/babybuddy)
Click the Gitpod badge to open a new development environment in Gitpod or use the
information and steps below to set up a local development environment for Baby Buddy.
## Requirements
- Python 3.6+, pip, pipenv
- NVM (NodeJS 14.x and NPM 7.x)
- Gulp
- Possibly `libpq-dev`
- This is necessary if `psycopg2` can't find an appropriate prebuilt binary.
## Installation
1. Install pipenv per [Installing pipenv](https://pipenv.pypa.io/en/latest/install/#installing-pipenv)
1. Install required Python packages, including dev packages
pipenv install --three --dev
- If this fails, install `libpq-dev` (e.g. `sudo apt install libpq-dev`) and try again.
1. Installed Node 14.x (if necessary)
nvm install 14
1. Activate Node 14.x
nvm use
1. Install Gulp CLI
npm install -g gulp-cli
1. Install required Node packages
npm install
1. Set, at least, the `DJANGO_SETTINGS_MODULE` environment variable
export DJANGO_SETTINGS_MODULE=babybuddy.settings.development
This process will differ based on the host OS. The above example is for
Linux-based systems. See [Configuration](../setup/configuration.md) for other
settings and methods for defining them.
1. Migrate the database
gulp migrate
1. Create cache table
gulp createcachetable
1. Build assets and run the server
gulp
This command will also watch for file system changes to rebuild assets and
restart the server as needed.
Open [http://127.0.0.1:8000](http://127.0.0.1:8000) and log in with the default
username and password (`admin`/`admin`).

View File

@ -0,0 +1,159 @@
# Gulp command reference
## Definitions
Baby Buddy's Gulp commands are defined in [`gulpfile.js`](https://github.com/babybuddy/babybuddy/tree/master/gulpfile.js).
Baby Buddy's management commands are defined in [`babybuddy/management/commands`](https://github.com/babybuddy/babybuddy/tree/master/babybuddy/management/commands).
## Commands
### `gulp`
Executes the `build` and `watch` commands and runs Django's development server.
This command also accepts the special parameter `--ip` for setting the
server IP address. See [`gulp runserver`](#runserver) for details.
### `build`
Creates all script, style and "extra" assets and places them in the
`babybuddy/static` folder.
### `docs:build`
Builds the documentation site in a local directory (`site` by default).
### `docs:deploy`
Deploys the documentation site to GitHub Pages.
### `docs:watch`
Runs a local server for the documentation site reloading whenever documentation
sites files (in the `docs` directory) as modified.
### `clean`
Deletes all build folders and the root `static` folder. Generally this should
be run before a `gulp build` to remove previous build files and the generated
static assets.
### `collectstatic`
Executes Django's `collectstatic` management task. This task relies on files in
the `babybuddy/static` folder, so generally `gulp build` should be run before
this command for production deployments. Gulp also passes along
non-overlapping arguments for this command, e.g. `--no-input`.
Note: a `SECRET_KEY` value must be set for this command to work.
### `compilemessages`
Executes Django's `compilemessages` management task. See [django-admin compilemessages](https://docs.djangoproject.com/en/4.0/ref/django-admin/#compilemessages)
for more details about other options and functionality of this command.
### `coverage`
Create a test coverage report. See [`.coveragerc`](https://github.com/babybuddy/babybuddy/tree/master/.coveragerc)
for default settings information.
### `createcachetable`
Executes Django's `createcachetable` management task. See [django-admin createcachetable](https://docs.djangoproject.com/en/4.0/ref/django-admin/#createcachetable)
for more details about other options and functionality of this command.
### `extras`
Copies "extra" files (fonts, images and server root contents) to the build
folder.
### `fake`
Adds some fake data to the database. By default, ``fake`` creates one child and
31 days of random data. Use the `--children` and `--days` flags to change the
default values, e.g. `gulp fake --children 5 --days 7` to generate five fake
children and seven days of data for each.
### `format`
Formats Baby Buddy's code base using [black](https://github.com/psf/black). Run this
before commits to ensure linting will pass!
### `generateschema`
Updates the [`openapi-schema.yml`](https://github.com/babybuddy/babybuddy/tree/master/openapi-schema.yml)
file in the project root based on current API functionality.
### `lint`
Executes Python and SASS linting for all relevant source files.
### `makemessages`
Executes Django's `makemessages` management task. See [django-admin makemessages](https://docs.djangoproject.com/en/4.0/ref/django-admin/#makemessages)
for more details about other options and functionality of this command. When
working on a single translation, the `-l` flag is useful to make message for
only that language, e.g. `gulp makemessages -l fr` to make only a French
language translation file.
### `makemigrations`
Executes Django's `makemigrations` management task. Gulp also passes along
non-overlapping arguments for this command.
### `migrate`
Executes Django's `migrate` management task. In addition to migrating the
database, this command creates the default `admin` user. Gulp also passes along
non-overlapping arguments for this command.
### `reset`
Resets the database to a default state *with* one fake child and 31 days of
fake data.
### `runserver`
Executes Django's `runserver` management task. Gulp passes non-overlapping
arguments for this command.
A special parameter, `--ip`, is also available to set the IP for the server.
E.g., execute `gulp runserver --ip 0.0.0.0:8000` to make the server available to
other devices on your local network.
### `scripts`
Builds and combines relevant application scripts. Generally this command does
not need to be executed independently - see the `build` command.
### `styles`
Builds and combines SASS styles in to CSS files. Generally this command does
not need to be executed independently - see the `build` command.
### `test`
Executes Baby Buddy's suite of tests.
Gulp also passes along non-overlapping arguments for this command, however
individual tests cannot be run with this command. `python manage.py test` can be
used for individual test execution.
### `updateglyphs`
Downloads generated glyph font files data from [Fonttello](https://fontello.com/)
based on [`config.json` file](https://github.com/babybuddy/babybuddy/tree/master/babybuddy/static_src/fontello/config.json). This
only needs to be run after making changes to the config file.
### `updatestatic`
Rebuilds Baby Buddy's `/static` folder by running the following commands in
order:
- [`lint`](#lint)
- [`clean`](#clean)
- [`build`](#build)
- [`collectstatic`](#collectstatic)
Execute and commit changes made by this command whenever changing Baby Buddy's
frontend code (SASS, JS, etc.).

View File

@ -0,0 +1,53 @@
# Pull requests
Baby Buddy's maintainers accept and encourage contributions via GitHub [Issues](https://github.com/babybuddy/babybuddy/issues)
and [Pull Requests](https://github.com/babybuddy/babybuddy/pulls). Maintainers
and users may also be found at [babybuddy/Lobby](https://gitter.im/babybuddy/Lobby)
on Gitter.
## Caveats
### Icon font
Baby Buddy uses [Fontello](https://fontello.com/) to build a custom icon font
for icons used throughout the application. See [`babybuddy/static_src/fontello`](https://github.com/babybuddy/babybuddy/tree/master/babybuddy/static_src/fontello)
for further documentation about using the config file with Fontello and license
information for fonts used by this application.
### Pipfile
If the [Pipfile](https://github.com/babybuddy/babybuddy/tree/master/Pipfile) is changed
the [requirements.txt](https://github.com/babybuddy/babybuddy/tree/master/requirements.txt)
must also be updated to reflect the change. This is necessary because hosting environments
do not provide adequate support for pipenv. To update the `requirements.txt` file to be in
sync with the `Pipenv` file run:
pipenv lock -r > requirements.txt
Add and commit the changes to the `requirements.txt` file.
### Static files
If static file assets (files in a `static_src` directory) are updated the production
static files (in the [`static` directory](https://github.com/babybuddy/babybuddy/tree/master/static))
must also be updated *and committed*. This is done because it prevents the need for Node
and related tooling in deployment environments. See [`gulp updatestatic`](gulp-command-reference.md#updatestatic)
for more information on how to update the static files.
### Translations
Modifying [locale files](https://github.com/babybuddy/babybuddy/tree/master/locale)
requires some extra steps. See [Translation](translation.md) for details.
## Pull request process
1. Fork this repository and commit your changes.
2. Make and commit tests for any new features or major functionality changes.
3. Run `gulp lint` and `gulp test` (see [Gulp command reference](gulp-command-reference.md))
and ensure that all tests pass.
4. Updated static assets if necessary and commit the `/static` folder (see [`gulp updatestatic`](gulp-command-reference.md#updatestatic)).
5. Open a [new pull request](https://github.com/babybuddy/babybuddy/compare) against
the `master` branch.
Maintainers will review new pull requests will as soon as possible, and we will
do our best to provide feedback and support potential contributors.

View File

@ -0,0 +1,59 @@
# Translation
## POEditor
Baby Buddy uses [POEditor](https://poeditor.com/) for translation contributions.
Interested contributors can [join translation of Baby Buddy](https://poeditor.com/join/project/QwQqrpTIzn)
for access to a simple, web-based frontend for adding/editing translation files
to the project.
## Manual
Baby Buddy has support for translation/localization. A manual translation
process will look something like this:
1. Set up a development environment (see [Development environment](development-environment.md)).
1. Run `gulp makemessages -l xx` where `xx` is a specific locale code in the
[ISO 639-1 format](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) (e.g.,
"il" for Italian or "es" for Spanish). This creates a new translation file at
`locale/xx/LC_MESSAGES/django.po`, or updates one if it exists.
1. Open the created/updated `django.po` file and update the header template
with license and contact info.
1. Start translating! Each translatable string will have a `msgid` value with
the string in English and a corresponding (empty) `msgstr` value where a
translated string can be filled in.
1. Once all strings have been translated, run `gulp compilemessages -l xx` to
compile an optimized translation file (`locale/xx/LC_MESSAGES/django.mo`).
1. To expose the new translation as a user setting, add the locale code to the
`LANGUAGES` array in the base settings file (`babybuddy/settings/base.py`).
1. Check if Plotly offers a translation (in `node_modules/plotly.js/dist/`) for
the language. If it does:
1. Add the Plotly translation file path to [`gulpfile.config.js`](https://github.com/babybuddy/babybuddy/tree/master/gulpfile.config.js)
in `scriptsConfig.graph`.
2. Build, collect, and commit the `/static` folder (see [`gulp updatestatic`](gulp-command-reference.md#updatestatic)).
1. Check if Moment offers a translation (in `node_modules/moment/locale/`) for
the language. If it does:
1. Add the Moment translation file path to [`gulpfile.config.js`](https://github.com/babybuddy/babybuddy/tree/master/gulpfile.config.js)
in `scriptsConfig.vendor`.
2. Build, collect, and commit the `/static` folder (see
[`gulp updatestatic`](gulp-command-reference.md#updatestatic)).
1. Run the development server, log in, and update the user language to test the
newly translated strings.
Once the translation is complete, commit the new files and changes to a fork
and [create a pull request](pull-requests.md) for review.
For more information on the Django translation process, see Django's
documentation section: [Translation](https://docs.djangoproject.com/en/4.0/topics/i18n/translation/).

View File

@ -39,5 +39,5 @@ and formats. All rows will be checked for errors on import and any issues will
be reported on screen and will need to be resolved before the import can be be reported on screen and will need to be resolved before the import can be
performed. performed.
See the [example import files](/core/tests/import) used for tests to get an idea See the [example import files](https://github.com/babybuddy/babybuddy/tree/master/core/tests/import)
of the expected data format. used for tests to get an idea of the expected data format.

View File

@ -5,44 +5,35 @@ Baby Buddy will check the application directory structure for an `.env` file or
take these variables from the system environment. **System environment variables take these variables from the system environment. **System environment variables
take precedence over the contents of an `.env` file.** take precedence over the contents of an `.env` file.**
- [`ALLOWED_HOSTS`](#allowed_hosts)
- [`ALLOW_UPLOADS`](#allow_uploads)
- [`AWS_ACCESS_KEY_ID`](#aws_access_key_id)
- [`AWS_SECRET_ACCESS_KEY`](#aws_secret_access_key)
- [`AWS_STORAGE_BUCKET_NAME`](#aws_storage_bucket_name)
- [`DEBUG`](#debug)
- [`NAP_START_MAX`](#nap_start_max)
- [`NAP_START_MIN`](#nap_start_min)
- [`DB_ENGINE`](#db_engine)
- [`DB_HOST`](#db_host)
- [`DB_NAME`](#db_name)
- [`DB_PASSWORD`](#db_password)
- [`DB_PORT`](#db_port)
- [`DB_USER`](#db_user)
- [`SECRET_KEY`](#secret_key)
- [`SECURE_PROXY_SSL_HEADER`](#secure_proxy_ssl_header)
- [`TIME_ZONE`](#time_zone)
- [`USE_24_HOUR_TIME_FORMAT`](#use_24_hour_time_format)
## `ALLOWED_HOSTS` ## `ALLOWED_HOSTS`
*Default: * (any)* *Default:* `*` (any host)
Set this variable to a single host or comma-separated list of hosts without spaces. Set this variable to a single host or comma-separated list of hosts without spaces.
This should *always* be set to a specific host or hosts in production deployments. This should *always* be set to a specific host or hosts in production deployments.
See also: [Django's documentation on the ALLOWED_HOSTS setting](https://docs.djangoproject.com/en/3.0/ref/settings/#allowed-hosts) Do not include schemes ("http" or "https") with this setting.
**Example value**
baby.example.test,baby.example2.test
**See also**
- [Django's documentation on the ALLOWED_HOSTS setting](https://docs.djangoproject.com/en/4.0/ref/settings/#allowed-hosts)
- [`CSRF_TRUSTED_ORIGINS`](#csrf_trusted_origins)
- [`SECURE_PROXY_SSL_HEADER`](#secure_proxy_ssl_header)
## `ALLOW_UPLOADS` ## `ALLOW_UPLOADS`
*Default: True* *Default:* `True`
Whether to allow uploads (e.g., of Child photos). For some deployments (Heroku) Whether to allow uploads (e.g., of Child photos). For some deployments (Heroku)
this setting will default to False due to the lack of available persistent storage. this setting will default to False due to the lack of available persistent storage.
## `AWS_ACCESS_KEY_ID` ## `AWS_ACCESS_KEY_ID`
*Default: None* *Default:* `None`
Required to access your AWS S3 bucket, should be uniquely generated per bucket Required to access your AWS S3 bucket, should be uniquely generated per bucket
for security. for security.
@ -51,7 +42,7 @@ See also: [`AWS_STORAGE_BUCKET_NAME`](#aws_storage_bucket_name)
## `AWS_SECRET_ACCESS_KEY` ## `AWS_SECRET_ACCESS_KEY`
*Default: None* *Default:* `None`
Required to access your AWS S3 bucket, should be uniquely generated per bucket Required to access your AWS S3 bucket, should be uniquely generated per bucket
for security. for security.
@ -60,116 +51,148 @@ See also: [`AWS_STORAGE_BUCKET_NAME`](#aws_storage_bucket_name)
## `AWS_STORAGE_BUCKET_NAME` ## `AWS_STORAGE_BUCKET_NAME`
*Default: None* *Default:* `None`
If you would like to use AWS S3 for storage on ephemeral storage platforms like If you would like to use AWS S3 for storage on ephemeral storage platforms like
Heroku you will need to create a bucket and add its name. See django-storages' Heroku you will need to create a bucket and add its name. See django-storages'
[Amazon S3 documentation](https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html). [Amazon S3 documentation](https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html).
## `DEBUG` ## `CSRF_TRUSTED_ORIGINS`
*Default: False* *Default:* `None`
When in debug mode, Baby Buddy will print much more detailed error information If Baby Buddy is behind a proxy, you may need add all possible origins to this setting
for exceptions. This setting should be *False* in production deployments. for form submission to work correctly. Separate multiple origins with commas.
See also [Django's documentation on the DEBUG setting](https://docs.djangoproject.com/en/3.0/ref/settings/#debug). Each entry must contain both the scheme (http, https) and fully-qualified domain name.
## `NAP_START_MAX` **Example value**
*Default: 18:00* https://baby.example.test,http://baby.example2.test,http://babybudy
The maximum nap *start* time (in the instance's time zone). Expects the 24-hour **See also**
format %H:%M.
## `NAP_START_MIN` - [Django's documentation on the `CSRF_TRUSTED_ORIGINS` setting](https://docs.djangoproject.com/en/4.0/ref/settings/#std:setting-CSRF_TRUSTED_ORIGINS)
- [`ALLOWED_HOSTS`](#allowed_hosts)
*Default: 06:00* - [`SECURE_PROXY_SSL_HEADER`](#secure_proxy_ssl_header)
The minimum nap *start* time (in the instance's time zone). Expects the 24-hour
format %H:%M.
## `DB_ENGINE` ## `DB_ENGINE`
*Default: django.db.backends.postgresql* *Default:* `django.db.backends.postgresql`
The database engine utilized for the deployment. The database engine utilized for the deployment.
See also [Django's documentation on the ENGINE setting](https://docs.djangoproject.com/en/3.0/ref/settings/#engine). See also [Django's documentation on the ENGINE setting](https://docs.djangoproject.com/en/4.0/ref/settings/#engine).
## `DB_HOST` ## `DB_HOST`
*Default: db* *Default:* `db`
The name of the database host for the deployment. The name of the database host for the deployment.
## `DB_NAME` ## `DB_NAME`
*Default: postgres* *Default:* `postgres`
The name of the database table utilized for the deployment. The name of the database table utilized for the deployment.
## `DB_PASSWORD` ## `DB_PASSWORD`
*No Default* *Default:* `None`
The password for the database user for the deployment. In the default example, The password for the database user for the deployment. In the default example,
this is the root PostgreSQL password. this is the root PostgreSQL password.
## `DB_PORT` ## `DB_PORT`
*Default: 5432* *Default:* `5432`
The listening port for the database. The default port is 5432 for PostgreSQL. The listening port for the database. The default port is 5432 for PostgreSQL.
## `DB_USER` ## `DB_USER`
*Default: postgres* *Default:* `postgres`
The database username utilized for the deployment. The database username utilized for the deployment.
## `DEBUG`
*Default:* `False`
When in debug mode, Baby Buddy will print much more detailed error information
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.
## `SECRET_KEY` ## `SECRET_KEY`
*Default: None* *Default:* `None`
A random, unique string must be set as the "secret key" before Baby Buddy can A random, unique string must be set as the "secret key" before Baby Buddy can
be deployed and run. be deployed and run.
See also [Django's documentation on the SECRET_KEY setting](https://docs.djangoproject.com/en/3.0/ref/settings/#secret-key). See also [Django's documentation on the SECRET_KEY setting](https://docs.djangoproject.com/en/4.0/ref/settings/#secret-key).
## `SECURE_PROXY_SSL_HEADER` ## `SECURE_PROXY_SSL_HEADER`
*Default: None* *Default:* `None`
If Baby Buddy is behind a proxy, you may need to set this to `True` in order to If Baby Buddy is behind a proxy, you may need to set this to `True` in order to
trust the `X-Forwarded-Proto` header that comes from your proxy, and any time trust the `X-Forwarded-Proto` header that comes from your proxy, and any time
its value is "https". This guarantees the request is secure (i.e., it originally its value is "https". This guarantees the request is secure (i.e., it originally
came in via HTTPS). came in via HTTPS).
:warning: Modifying this setting can compromise Baby Buddys security. Ensure **See also**
you fully understand your setup before changing it.
See also [Django's documentation on the SECURE_PROXY_SSL_HEADER setting](https://docs.djangoproject.com/en/3.0/ref/settings/#secure-proxy-ssl-header). - [Django's documentation on the SECURE_PROXY_SSL_HEADER setting](https://docs.djangoproject.com/en/4.0/ref/settings/#secure-proxy-ssl-header)
- [`ALLOWED_HOSTS`](#allowed_hosts)
- [`CSRF_TRUSTED_ORIGINS`](#csrf_trusted_origins)
## `SUB_PATH`
*Default:* `None`
If Baby Buddy is hosted in a subdirectory of another server (e.g., `http://www.example.com/babybuddy`)
this must be set to the subdirectory path (e.g., `/babybuddy`) for correct handling of
application configuration.
Additional steps are required! See [Subdirectory configuration](subdirectory.md) for
details.
## `TIME_ZONE` ## `TIME_ZONE`
*Default: UTC* *Default:* `UTC`
The default time zone to use for the instance. See [List of tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones) The default time zone to use for the instance. This value can be overridden per use from
for all possible values. This value can be overridden per use from the user the user settings form.
settings form.
**Example value**
America/Los_Angeles
**See also**
[List of tz database time zones](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones)
## `USE_24_HOUR_TIME_FORMAT` ## `USE_24_HOUR_TIME_FORMAT`
*Default: False* *Default:* `False`
Whether to force 24-hour time format for locales that do not ordinarily use it Whether to force 24-hour time format for locales that do not ordinarily use it
(e.g. `en`). Support for this feature must be implemented on a per-locale basis. (e.g. `en`). Support for this feature must be implemented on a per-locale basis.
See format files under [`babybuddy/formats`](/babybuddy/formats) for supported See format files under [`babybuddy/formats`](https://github.com/babybuddy/babybuddy/tree/master/babybuddy/formats)
locales. for supported locales.
Note: Baby Buddy interprets this value as a boolean from a string
using Python's built-in [`strtobool`](https://docs.python.org/3/distutils/apiref.html#distutils.util.strtobool)
tool. Only certain strings will work (e.g., "True" for `True` and "False" for
`False`), other unrecognized strings will cause a `ValueError` and prevent Baby
Buddy from loading.

View File

@ -4,7 +4,7 @@ The default username and password for Baby Buddy is `admin`/`admin`. For any
deployment, **log in and change the default password immediately**. deployment, **log in and change the default password immediately**.
Many of Baby Buddy's configuration settings can be controlled using environment Many of Baby Buddy's configuration settings can be controlled using environment
variables - see [Configuration](/docs/setup/configuration.md) for detailed information. variables - see [Configuration](configuration.md) for detailed information.
## Docker ## Docker
@ -18,7 +18,7 @@ configuration as a template to get started quickly:
version: "2.1" version: "2.1"
services: services:
babybuddy: babybuddy:
image: ghcr.io/linuxserver/babybuddy image: lscr.io/linuxserver/babybuddy
container_name: babybuddy container_name: babybuddy
environment: environment:
- TZ=UTC - TZ=UTC
@ -29,9 +29,7 @@ services:
restart: unless-stopped restart: unless-stopped
``` ```
:warning: Baby Buddy v1.7.0 was the final version deployed to See [HTTPS/SSL configuration](ssl.md) for information on how to secure Baby Buddy.
[babybuddy/babybuddy](https://hub.docker.com/r/babybuddy/babybuddy) on Docker Hub
Future versions of Baby Buddy will use the LSIO container.
For doing administrative work within the LSIO container, setting an environment variable may be necessary. For doing administrative work within the LSIO container, setting an environment variable may be necessary.
For example: For example:
@ -49,12 +47,12 @@ python3 /app/babybuddy/manage.py clearsessions
For manual deployments to Heroku without using the "deploy" button, make sure to For manual deployments to Heroku without using the "deploy" button, make sure to
create the following settings before pushing: create the following settings before pushing:
heroku config:set DISABLE_COLLECTSTATIC=1
heroku config:set DJANGO_SETTINGS_MODULE=babybuddy.settings.heroku heroku config:set DJANGO_SETTINGS_MODULE=babybuddy.settings.heroku
heroku config:set SECRET_KEY=<CHANGE TO SOMETHING RANDOM> heroku config:set SECRET_KEY=<CHANGE TO SOMETHING RANDOM>
heroku config:set DISABLE_COLLECTSTATIC=1
heroku config:set TIME_ZONE=<DESIRED DEFAULT TIMEZONE> heroku config:set TIME_ZONE=<DESIRED DEFAULT TIMEZONE>
See [Configuration](/docs/setup/configuration.md) for other settings that can be controlled See [Configuration](configuration.md) for other settings that can be controlled
by `heroku config:set`. by `heroku config:set`.
After an initial push, execute the following commands: After an initial push, execute the following commands:
@ -76,115 +74,124 @@ requirements are Python, a web server, an application server, and a database.
### Example deployment ### Example deployment
*This example assumes a 512 MB VPS instance with Ubuntu 18.04.* It uses Python 3.6+, *This example assumes a 1 GB VPS instance with Ubuntu 20.04.* It uses Python 3.8,
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 1+ child). and any number of children).
1. Install system packages 1. Install system packages
sudo apt-get install python3 python3-pip nginx uwsgi uwsgi-plugin-python3 git libopenjp2-7-dev libpq-dev sudo apt-get install python3 python3-pip nginx uwsgi uwsgi-plugin-python3 git libopenjp2-7-dev libpq-dev
1. Default python3 to python for this session 2. Default python3 to python for this session
alias python=python3 alias python=python3
1. Install pipenv 3. Install pipenv
sudo -H pip3 install pipenv sudo -H pip3 install pipenv
1. Set up directories and files 4. Set up directories and files
sudo mkdir /var/www/babybuddy sudo mkdir /var/www/babybuddy
sudo chown $USER:$(id -gn $USER) /var/www/babybuddy sudo chown $USER:$(id -gn $USER) /var/www/babybuddy
mkdir -p /var/www/babybuddy/data/media mkdir -p /var/www/babybuddy/data/media
git clone https://github.com/babybuddy/babybuddy.git /var/www/babybuddy/public git clone https://github.com/babybuddy/babybuddy.git /var/www/babybuddy/public
1. Move in to the application folder 5. Move in to the application folder
cd /var/www/babybuddy/public cd /var/www/babybuddy/public
1. Set pipenv to install locally. 6. Initiate and enter a Python environment with Pipenv locally.
export PIPENV_VENV_IN_PROJECT=1 export PIPENV_VENV_IN_PROJECT=1
1. Initiate and enter the Python environment
pipenv install --three pipenv install --three
pipenv shell pipenv shell
1. Create a production settings file and set the ``SECRET_KEY`` and ``ALLOWED_HOSTS`` values 7. Create a production settings file and set the ``SECRET_KEY`` and ``ALLOWED_HOSTS`` values
cp babybuddy/settings/production.example.py babybuddy/settings/production.py cp babybuddy/settings/production.example.py babybuddy/settings/production.py
editor babybuddy/settings/production.py editor babybuddy/settings/production.py
1. Initiate the application 8. Initiate the application
export DJANGO_SETTINGS_MODULE=babybuddy.settings.production export DJANGO_SETTINGS_MODULE=babybuddy.settings.production
python manage.py migrate python manage.py migrate
python manage.py createcachetable python manage.py createcachetable
1. Set appropriate permissions on the database and data folder 9. Set appropriate permissions on the database and data folder
sudo chown -R www-data:www-data /var/www/babybuddy/data sudo chown -R www-data:www-data /var/www/babybuddy/data
sudo chmod 640 /var/www/babybuddy/data/db.sqlite3 sudo chmod 640 /var/www/babybuddy/data/db.sqlite3
sudo chmod 750 /var/www/babybuddy/data sudo chmod 750 /var/www/babybuddy/data
1. Create and configure the uwsgi app 10. Create and configure the uwsgi app
sudo editor /etc/uwsgi/apps-available/babybuddy.ini sudo editor /etc/uwsgi/apps-available/babybuddy.ini
Example config: Example config:
[uwsgi] ```ini
plugins = python3 [uwsgi]
project = babybuddy plugins = python3
base_dir = /var/www/babybuddy project = babybuddy
base_dir = /var/www/babybuddy
chdir = %(base_dir)/public chdir = %(base_dir)/public
virtualenv = %(chdir)/.venv virtualenv = %(chdir)/.venv
module = %(project).wsgi:application module = %(project).wsgi:application
env = DJANGO_SETTINGS_MODULE=%(project).settings.production env = DJANGO_SETTINGS_MODULE=%(project).settings.production
master = True master = True
vacuum = True vacuum = True
```
See the [uWSGI documentation](http://uwsgi-docs.readthedocs.io/en/latest/) See the [uWSGI documentation](http://uwsgi-docs.readthedocs.io/en/latest/)
for more advanced configuration details. for more advanced configuration details.
1. Symlink config and restart uWSGI: See [Subdirectory configuration](subdirectory.md) for additional configuration
required if Baby Buddy will be hosted in a subdirectory of another server.
sudo ln -s /etc/uwsgi/apps-available/babybuddy.ini /etc/uwsgi/apps-enabled/babybuddy.ini 11. Symlink config and restart uWSGI:
sudo service uwsgi restart
1. Create and configure the nginx server sudo ln -s /etc/uwsgi/apps-available/babybuddy.ini /etc/uwsgi/apps-enabled/babybuddy.ini
sudo service uwsgi restart
sudo editor /etc/nginx/sites-available/babybuddy 12. Create and configure the nginx server
Example config: sudo editor /etc/nginx/sites-available/babybuddy
upstream babybuddy { Example config:
server unix:///var/run/uwsgi/app/babybuddy/socket;
}
server { ```nginx
listen 80; upstream babybuddy {
server_name babybuddy.example.com; server unix:///var/run/uwsgi/app/babybuddy/socket;
}
server {
listen 80;
server_name babybuddy.example.com;
location / {
uwsgi_pass babybuddy;
include uwsgi_params;
}
location /media {
alias /var/www/babybuddy/data/media;
}
}
```
location / { See the [nginx documentation](https://nginx.org/en/docs/) for more advanced
uwsgi_pass babybuddy; configuration details.
include uwsgi_params;
}
location /media {
alias /var/www/babybuddy/data/media;
}
}
See the [nginx documentation](https://nginx.org/en/docs/) for more advanced See [Subdirectory configuration](subdirectory.md) for additional configuration
configuration details. required if Baby Buddy will be hosted in a subdirectory of another server.
1. Symlink config and restart NGINX: 14. Symlink config and restart NGINX:
sudo ln -s /etc/nginx/sites-available/babybuddy /etc/nginx/sites-enabled/babybuddy sudo ln -s /etc/nginx/sites-available/babybuddy /etc/nginx/sites-enabled/babybuddy
sudo service nginx restart sudo service nginx restart
1. That's it (hopefully)! :tada: 15. That's it (hopefully)!
See [HTTPS/SSL configuration](ssl.md) for information on how to secure Baby Buddy.

53
docs/setup/proxy.md Normal file
View File

@ -0,0 +1,53 @@
# Proxy configuration
Configuring Baby Buddy to run behind a proxy may require some additional configuration
depending on the individual proxy configuration. Baby Buddy's environment variables for
configuration should allow most proxy setups to work, but it may require some testing
and tweaking of settings.
## Important configuration
### [`CSRF_TRUSTED_ORIGINS`](../configuration#csrf_trusted_origins)
[Cross Site Request Forgery](https://owasp.org/www-community/attacks/csrf) protection is
an important way to prevent malicious users from sening fake requests to Baby Buddy to
read, alter, or destroy data.
To protect against this threat Baby Buddy checks the `Origin` header of certain requests
to ensure that it matches a "trusted" origin for the application. If the origin and host
are the same CSRF will pass without any extra configuration but if the two are different
the origin must be in `CSRF_TRUSTED_ORIGINS` to pass.
For example if Baby Buddy is configured in a container with a private network and a host
`babybuddy` that is exposed publicly by a proxy (e.g., nginx) at the address
`https://baby.example.com` then form submissions from browsers will have an `Origin` of
`https://baby.example.com` that *does not match* the host `babybudy`. This will cause a
CSRF error and the request will be rejected with a `403 Forbidden` error. To support
this example configuration the environment variable `CSRF_TRUSTED_ORIGINS` should be set
to the full public address (including the scheme): `https://baby.example.com` for CSRF
protected requests to succeed.
Note: multiple origins can be added by separating origins with commas. E.g.:
CSRF_TRUSTED_ORIGINS=https://baby.example.com,https://baby.example.org
### [`SECURE_PROXY_SSL_HEADER`](../configuration#secure_proxy_ssl_header)
If Baby Buddy is configured behind a standard HTTP proxy requests will always been seen
as insecure even if the exposed public connection uses HTTPS between the client and
proxy.
To address this most proxies can be configured to pass a special header to Baby Buddy
indicating the scheme used by the original request. [`X-Forwarded-Proto`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Proto)
is a common standard header for this feature and it is currently the only header
supported by Baby Buddy. To use this feature the `SECURE_PROXY_SSL_HEADER` environment
variable to `True` and Baby Buddy will consider the scheme indicated by the
`X-Forwarded-Proto` header to be the scheme used for the request.
**Additional Resources**
- [Caddy Reverse Proxy Defaults](https://caddyserver.com/docs/caddyfile/directives/reverse_proxy#defaults)
- [NGINX - Using the `Forwarded` Header](https://www.nginx.com/resources/wiki/start/topics/examples/forwarded/)
(Note: NGINX treats `X-Forwarded-Proto` as legacy. See the bottom of this resource for relevant information.)
- [Redirect HTTP to HTTPS with HAProxy](https://www.haproxy.com/blog/redirect-http-to-https-with-haproxy/)
- [Traefik Routing - EntryPoints - Forwarded Headers](https://doc.traefik.io/traefik/v2.3/routing/entrypoints/#forwarded-headers)

194
docs/setup/ssl.md Normal file
View File

@ -0,0 +1,194 @@
# HTTPS/SSL configuration
The example Docker and manual deployment methods do not include HTTPS/SSL by default.
Additional tools and configuration are required to add HTTPS support.
## Configuration requirements
For either approach (host- or container-based) Baby Buddy's configuration will need to
be updated to account for the proxy. For details on these settings see [Proxy configuration](proxy.md).
After configuring the proxy set the following two environment variables and then restart
necessary services:
```ini
CSRF_TRUSTED_ORIGINS=https://babybuddy.example.com
SECURE_PROXY_SSL_HEADER=True
```
## Host-based proxy
This guide assumes Baby Buddy has been deployed to a Debian-like system with
[snapd installed](https://snapcraft.io/docs/installing-snapd) using the [example deployment](deployment.md#example-deployment)
however this approach can also be used with a Docker deployment if having the proxy
in the host is desired (otherwise see [Container-based proxy](#container-based-proxy)).
If the example deployment with uWSGI and NGINX is already used skip to [Install Certbot](#install-certbot)
and [Obtain and install certificate](#obtain-and-install-certificate).
### Install NGINX
If NGINX is not already installed on the host system install it with a package manager.
```shell
apt-get -y install nginx
```
NGINX will be used to proxy HTTPS traffic to Baby Buddy. There are many other proxies
available for this (often with Let's Encrypt support, as well) so a different one can
be used if desired.
#### Configure NGINX
If Baby Buddy is running from Docker a new NGINX site will need to be created to send
traffic to Docker. The configuration below uses the example domain `babybuddy.example.com`
and assumes Docker has exposed Baby Buddy on port `8000` (the default configuration).
```shell
editor /etc/nginx/sites-available/babybuddy
```
Initial configuration:
```nginx
server_tokens off;
access_log /var/log/nginx/babybuddy.access.log;
error_log /var/log/nginx/babybuddy.error.log;
server {
server_name babybuddy.example.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
}
}
```
Enable the new site:
```shell
ln -s /etc/nginx/sites-available/babybuddy /etc/nginx/sites-enabled/babybuddy
service nginx restart
```
Confirm the site is not accessible at `http://babybuddy.example.com`. Note: Attempting
to log in will result in a CSRF error! This will be addressed after HTTPS has been
established.
### Install Certbot
This example uses [Let's Encrypt's](https://letsencrypt.org/) free service for obtaining
SSL certificates. Other methods can be used to obtain and install a certificate as
desired.
[Certbot](https://certbot.eff.org/instructions) is used to obtain free SSL certificates
from Let's Encrypt.
```shell
snap install core && snap refresh core
snap install --classic certbot
ln -s /snap/bin/certbot /usr/bin/certbot
```
### Obtain and install certificate
The following command will ask for an email address to register with Let's Encrypt and
then prompt a service agreement and which NGINX host to obtain a certificate for. The
certificate will be installed and activated automatically.
```shell
certbot --nginx
[answers prompts as required]
service nginx restart
```
Certbot should have updated the NGINX site configuration (`/etc/nginx/sites-available/babybuddy`)
to look something like this:
```nginx
server_tokens off;
access_log /var/log/nginx/babybuddy.access.log;
error_log /var/log/nginx/babybuddy.error.log;
server {
server_name babybuddy.example.com;
location / {
proxy_pass http://localhost:8000;
proxy_set_header Host $host;
}
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/babybuddy.example.com/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/babybuddy.example.com/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
}
server {
if ($host = babybuddy.example.com) {
return 301 https://$host$request_uri;
} # managed by Certbot
server_name babybuddy.example.com;
listen 80;
return 404; # managed by Certbot
}
```
If the certificate was obtained by some other means the configuration about should be
instructive for how to add it to the NGINX site configuration.
## Container-based proxy
If Baby Buddy is already hosted in a Docker container the proxy (NGINX) can be hosted
there as well. The configuration provided here assumes the `docker-compose.yml` example
from the [Docker deployment method](deployment.md#docker) is used.
### Add NGINX service
Add the following `services` entry to `docker-compose.yml`:
```yaml
babybuddy-nginx:
image: nginx
container_name: babybuddy-nginx
volumes:
- /path/to/appdata/nginx.conf:/etc/nginx/conf.d/default.conf
- /path/to/appdata/logs:/var/log/nginx
- /path/to/appdata/certs:/certs
ports:
- 80:80
- 443:443
depends_on:
- babybuddy
```
Set the contents of `/path/to/appdata/nginx.conf` to:
```nginx
server {
server_name babybuddy.example.com;
listen 443 ssl;
ssl_certificate /certs/babybuddy.example.com.crt;
ssl_certificate_key /certs/babybuddy.example.com.key;
location / {
proxy_pass http://babybuddy:8000;
proxy_set_header Host $host;
}
}
server {
if ($host = babybuddy.example.com) {
return 301 https://$host$request_uri;
}
server_name babybuddy.example.com;
listen 80;
return 404;
}
```
### Add certificates
Place certificates in `/path/to/appdata/certs` using the files name of `ssl_certificate`
and `ssl_ceritifcate_key` in the NGINX configuration.

View File

@ -0,0 +1,61 @@
# Subdirectory configuration
Baby Buddy's default configuration assumes deployment in to the root of a web server.
Some additional configuration is required to install Baby Buddy in a subdirectory of a
server instead (e.g., to `http://www.example.com/babybuddy`).
## Minimum version
Baby Buddy added full support for subdirectory installing in version **1.10.2**. While
it is still possible to do a subdirectory installation in older versions of Baby Buddy
it is not recommended.
## [`SUB_PATH`](../configuration#sub_path)
Set this environment variable to the subdirectory of the Baby Buddy installation. E.g.,
`SUB_PATH=/babybuddy` if the desired URL is `http://www.example.com/babybuddy`).
## uWSGI + NGINX configuration
When using uWSGI and NGINX (as in the [example deployment](deployment.md#example-deployment))
the following configurations are required.
*Assume the subdirectory `babybuddy` for configuration change examples below but this
can be anything includes multiple subdirectories (e.g., `/my/apps/babybuddy`). Other
paths used in these examples also assume a configuration based on the
[example deployment](deployment.md#example-deployment).*
### uWSGI
In the app configuration replace the `module` declaration with a `mount` declaration and
add the `manage-script-name` declaration and [`SUB_PATH`](../configuration#sub_path)
environment variable to the `[uwsgi]` configuration block.
``` diff
- module = %(project).wsgi:application
+ mount = /babybuddy=%(project).wsgi:application
+ manage-script-name = true
+ env = SUB_PATH=/babybuddy
```
### NGINX
Alter the NGINX `server` configuration to include the desired subdirectory path in the
app (Baby Buddy root) and media `location` declarations *and* add a new declaration for
the static `location`.
``` diff
- location / {
+ location /babybuddy {
```
``` diff
+ location /babybuddy/static {
+ alias /var/www/babybuddy/public/static;
+ }
```
``` diff
- location /media {
+ location /babybuddy/media {
```

View File

@ -1,4 +1,4 @@
var basePath = 'babybuddy/static/babybuddy/'; const basePath = 'babybuddy/static/babybuddy/';
module.exports = { module.exports = {
basePath: basePath, basePath: basePath,
@ -43,6 +43,7 @@ module.exports = {
'node_modules/moment/locale/pt.js', 'node_modules/moment/locale/pt.js',
'node_modules/moment/locale/sv.js', 'node_modules/moment/locale/sv.js',
'node_modules/moment/locale/tr.js', 'node_modules/moment/locale/tr.js',
'node_modules/moment/locale/zh-cn.js',
'node_modules/moment-timezone/builds/moment-timezone-with-data-10-year-range.js', 'node_modules/moment-timezone/builds/moment-timezone-with-data-10-year-range.js',
'node_modules/tempusdominus-bootstrap-4/build/js/tempusdominus-bootstrap-4.js' 'node_modules/tempusdominus-bootstrap-4/build/js/tempusdominus-bootstrap-4.js'
], ],
@ -59,7 +60,8 @@ module.exports = {
'node_modules/plotly.js/dist/plotly-locale-pt-pt.js', 'node_modules/plotly.js/dist/plotly-locale-pt-pt.js',
'node_modules/plotly.js/dist/plotly-locale-sv.js', 'node_modules/plotly.js/dist/plotly-locale-sv.js',
'node_modules/plotly.js/dist/plotly-locale-tr.js', 'node_modules/plotly.js/dist/plotly-locale-tr.js',
'node_modules/plotly.js/dist/plotly-locale-uk.js' 'node_modules/plotly.js/dist/plotly-locale-uk.js',
'node_modules/plotly.js/dist/plotly-locale-zh-cn.js',
], ],
app: [ app: [
'babybuddy/static_src/js/babybuddy.js', 'babybuddy/static_src/js/babybuddy.js',

View File

@ -1,17 +1,18 @@
var gulp = require('gulp'); const gulp = require('gulp');
var concat = require('gulp-concat'); const concat = require('gulp-concat');
var del = require('del'); const del = require('del');
var es = require('child_process').execSync; const es = require('child_process').execSync;
var flatten = require('gulp-flatten'); const flatten = require('gulp-flatten');
var fontello = require('gulp-fontello'); const fontello = require('gulp-fontello');
var pump = require('pump'); const pump = require('pump');
var sass = require('gulp-sass')(require('sass')); const removeSourcemaps = require('gulp-remove-sourcemaps');
var sassGlob = require('gulp-sass-glob'); const sass = require('gulp-sass')(require('sass'));
var styleLint = require('gulp-stylelint'); const sassGlob = require('gulp-sass-glob');
var spawn = require('child_process').spawn; const styleLint = require('gulp-stylelint');
const spawn = require('child_process').spawn;
var config = require('./gulpfile.config.js'); const config = require('./gulpfile.config.js');
/** /**
* Support functions for Gulp tasks. * Support functions for Gulp tasks.
@ -180,18 +181,21 @@ function lint(cb) {
function scripts(cb) { function scripts(cb) {
pump([ pump([
gulp.src(config.scriptsConfig.vendor), gulp.src(config.scriptsConfig.vendor),
removeSourcemaps(),
concat('vendor.js'), concat('vendor.js'),
gulp.dest(config.scriptsConfig.dest) gulp.dest(config.scriptsConfig.dest)
], cb); ], cb);
pump([ pump([
gulp.src(config.scriptsConfig.graph), gulp.src(config.scriptsConfig.graph),
removeSourcemaps(),
concat('graph.js'), concat('graph.js'),
gulp.dest(config.scriptsConfig.dest) gulp.dest(config.scriptsConfig.dest)
], cb); ], cb);
pump([ pump([
gulp.src(config.scriptsConfig.app), gulp.src(config.scriptsConfig.app),
removeSourcemaps(),
concat('app.js'), concat('app.js'),
gulp.dest(config.scriptsConfig.dest) gulp.dest(config.scriptsConfig.dest)
], cb); ], cb);
@ -224,7 +228,7 @@ function styles(cb) {
* @param cb * @param cb
*/ */
function test(cb) { function test(cb) {
var command = [ let command = [
'run', 'run',
'python', 'python',
'manage.py', 'manage.py',
@ -277,12 +281,12 @@ function watch() {
*/ */
gulp.task('collectstatic', function(cb) { gulp.task('collectstatic', function(cb) {
var command = ['run', 'python', 'manage.py', 'collectstatic']; let command = ['run', 'python', 'manage.py', 'collectstatic'];
/* Use base settings if no settings parameter is supplied. */ /* Use base settings if no settings parameter is supplied. */
var parameters = process.argv.splice(3); const parameters = process.argv.splice(3);
var noSettings = true; let noSettings = true;
for (var i = 0; i < parameters.length; i++) { for (let i = 0; i < parameters.length; i++) {
if (parameters[i].substring(0, 10) === '--settings') { if (parameters[i].substring(0, 10) === '--settings') {
noSettings = false; noSettings = false;
break; break;
@ -325,15 +329,15 @@ gulp.task('reset', function(cb) {
}); });
gulp.task('runserver', function(cb) { gulp.task('runserver', function(cb) {
var command = ['run', 'python', 'manage.py', 'runserver']; let command = ['run', 'python', 'manage.py', 'runserver'];
/** /**
* Process any parameters. Any arguments found here will be removed from * Process any parameters. Any arguments found here will be removed from
* the parameters list so other parameters continue to be passed to the * the parameters list so other parameters continue to be passed to the
* command. * command.
**/ **/
var parameters = process.argv.splice(2); const parameters = process.argv.splice(2);
for (var i = 0; i < parameters.length; i++) { for (let i = 0; i < parameters.length; i++) {
/* May be included because this is the default gulp command. */ /* May be included because this is the default gulp command. */
if (parameters[i] === 'runserver') { if (parameters[i] === 'runserver') {
delete parameters[i]; delete parameters[i];

Binary file not shown.

View File

@ -121,22 +121,26 @@ msgstr "Zeitzone"
msgid "{user}'s Settings" msgid "{user}'s Settings"
msgstr "{user} Einstellungen" msgstr "{user} Einstellungen"
#: babybuddy/settings/base.py:166
msgid "Chinese (simplified)"
msgstr ""
#: babybuddy/settings/base.py:167 #: babybuddy/settings/base.py:167
msgid "Dutch"
msgstr ""
#: babybuddy/settings/base.py:168
#, fuzzy #, fuzzy
#| msgid "English" #| msgid "English"
msgid "English (US)" msgid "English (US)"
msgstr "Englisch" msgstr "Englisch"
#: babybuddy/settings/base.py:168 #: babybuddy/settings/base.py:169
#, fuzzy #, fuzzy
#| msgid "English" #| msgid "English"
msgid "English (UK)" msgid "English (UK)"
msgstr "Englisch" msgstr "Englisch"
#: babybuddy/settings/base.py:169
msgid "Dutch"
msgstr ""
#: babybuddy/settings/base.py:170 #: babybuddy/settings/base.py:170
msgid "French" msgid "French"
msgstr "Französisch" msgstr "Französisch"
@ -173,18 +177,6 @@ msgstr "Schwedisch"
msgid "Turkish" msgid "Turkish"
msgstr "Türkisch" msgstr "Türkisch"
#: babybuddy/templates/403.html:4 babybuddy/templates/403.html:7
msgid "Permission Denied"
msgstr "Zugriff verweigert"
#: babybuddy/templates/403.html:12
msgid ""
"You do not have permission to access this resource. Contact a site "
"administrator for assistance."
msgstr ""
"Du hast keine Berechtigung auf diese Ressource zuzugreifen. Für "
"Unterstützung kontaktiere bitte den Administrator."
#: babybuddy/templates/admin/base_site.html:4 #: babybuddy/templates/admin/base_site.html:4
#: babybuddy/templates/admin/base_site.html:7 #: babybuddy/templates/admin/base_site.html:7
#: babybuddy/templates/babybuddy/nav-dropdown.html:338 #: babybuddy/templates/babybuddy/nav-dropdown.html:338
@ -230,27 +222,27 @@ msgstr ""
msgid "Quick Start Timer" msgid "Quick Start Timer"
msgstr "Quick-Start Timer" msgstr "Quick-Start Timer"
#: babybuddy/templates/babybuddy/nav-dropdown.html:51 core/models.py:216 #: babybuddy/templates/babybuddy/nav-dropdown.html:51 core/models.py:158
#: core/models.py:220 #: core/models.py:162
msgid "Diaper Change" msgid "Diaper Change"
msgstr "Windeln wechseln" msgstr "Windeln wechseln"
#: babybuddy/templates/babybuddy/nav-dropdown.html:57 #: babybuddy/templates/babybuddy/nav-dropdown.html:57
#: babybuddy/templates/babybuddy/nav-dropdown.html:274 core/models.py:285 #: babybuddy/templates/babybuddy/nav-dropdown.html:274 core/models.py:227
#: core/models.py:289 core/templates/core/timer_detail.html:43 #: core/models.py:231 core/templates/core/timer_detail.html:43
msgid "Feeding" msgid "Feeding"
msgstr "Mahlzeit" msgstr "Mahlzeit"
#: babybuddy/templates/babybuddy/nav-dropdown.html:63 #: babybuddy/templates/babybuddy/nav-dropdown.html:63
#: babybuddy/templates/babybuddy/nav-dropdown.html:150 core/models.py:307 #: babybuddy/templates/babybuddy/nav-dropdown.html:150 core/models.py:250
#: core/models.py:318 core/models.py:322 core/templates/core/note_list.html:29 #: core/models.py:260 core/models.py:264 core/templates/core/note_list.html:29
msgid "Note" msgid "Note"
msgstr "Notiz" msgstr "Notiz"
#: babybuddy/templates/babybuddy/nav-dropdown.html:69 #: babybuddy/templates/babybuddy/nav-dropdown.html:69
#: babybuddy/templates/babybuddy/nav-dropdown.html:281 #: babybuddy/templates/babybuddy/nav-dropdown.html:281
#: babybuddy/templates/babybuddy/welcome.html:42 core/models.py:350 #: babybuddy/templates/babybuddy/welcome.html:42 core/models.py:292
#: core/models.py:351 core/models.py:354 #: core/models.py:293 core/models.py:296
#: core/templates/core/sleep_confirm_delete.html:7 #: core/templates/core/sleep_confirm_delete.html:7
#: core/templates/core/sleep_form.html:13 core/templates/core/sleep_list.html:4 #: core/templates/core/sleep_form.html:13 core/templates/core/sleep_list.html:4
#: core/templates/core/sleep_list.html:7 core/templates/core/sleep_list.html:12 #: core/templates/core/sleep_list.html:7 core/templates/core/sleep_list.html:12
@ -259,8 +251,8 @@ msgid "Sleep"
msgstr "Schlafen" msgstr "Schlafen"
#: babybuddy/templates/babybuddy/nav-dropdown.html:75 #: babybuddy/templates/babybuddy/nav-dropdown.html:75
#: babybuddy/templates/babybuddy/nav-dropdown.html:172 core/models.py:389 #: babybuddy/templates/babybuddy/nav-dropdown.html:172 core/models.py:331
#: core/models.py:399 core/models.py:400 core/models.py:403 #: core/models.py:341 core/models.py:342 core/models.py:345
#: core/templates/core/temperature_confirm_delete.html:7 #: core/templates/core/temperature_confirm_delete.html:7
#: core/templates/core/temperature_form.html:13 #: core/templates/core/temperature_form.html:13
#: core/templates/core/temperature_list.html:4 #: core/templates/core/temperature_list.html:4
@ -272,8 +264,8 @@ msgstr "Temperatur"
#: babybuddy/templates/babybuddy/nav-dropdown.html:81 #: babybuddy/templates/babybuddy/nav-dropdown.html:81
#: babybuddy/templates/babybuddy/nav-dropdown.html:294 #: babybuddy/templates/babybuddy/nav-dropdown.html:294
#: babybuddy/templates/babybuddy/welcome.html:50 core/models.py:526 #: babybuddy/templates/babybuddy/welcome.html:50 core/models.py:468
#: core/models.py:527 core/models.py:530 #: core/models.py:469 core/models.py:472
#: core/templates/core/timer_detail.html:59 #: core/templates/core/timer_detail.html:59
#: core/templates/core/tummytime_confirm_delete.html:7 #: core/templates/core/tummytime_confirm_delete.html:7
#: core/templates/core/tummytime_form.html:13 #: core/templates/core/tummytime_form.html:13
@ -284,8 +276,8 @@ msgid "Tummy Time"
msgstr "Bauchzeit" msgstr "Bauchzeit"
#: babybuddy/templates/babybuddy/nav-dropdown.html:87 #: babybuddy/templates/babybuddy/nav-dropdown.html:87
#: babybuddy/templates/babybuddy/nav-dropdown.html:186 core/models.py:552 #: babybuddy/templates/babybuddy/nav-dropdown.html:186 core/models.py:494
#: core/models.py:561 core/models.py:562 core/models.py:565 #: core/models.py:503 core/models.py:504 core/models.py:507
#: core/templates/core/weight_confirm_delete.html:7 #: core/templates/core/weight_confirm_delete.html:7
#: core/templates/core/weight_form.html:13 #: core/templates/core/weight_form.html:13
#: core/templates/core/weight_list.html:4 #: core/templates/core/weight_list.html:4
@ -307,7 +299,7 @@ msgid "Timeline"
msgstr "" msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:122 #: babybuddy/templates/babybuddy/nav-dropdown.html:122
#: babybuddy/templates/babybuddy/nav-dropdown.html:130 core/models.py:159 #: babybuddy/templates/babybuddy/nav-dropdown.html:130 core/models.py:101
#: core/templates/core/child_confirm_delete.html:7 #: core/templates/core/child_confirm_delete.html:7
#: core/templates/core/child_detail.html:7 #: core/templates/core/child_detail.html:7
#: core/templates/core/child_form.html:13 core/templates/core/child_list.html:4 #: core/templates/core/child_form.html:13 core/templates/core/child_list.html:4
@ -317,10 +309,10 @@ msgstr ""
msgid "Children" msgid "Children"
msgstr "Kinder" msgstr "Kinder"
#: babybuddy/templates/babybuddy/nav-dropdown.html:136 core/models.py:158 #: babybuddy/templates/babybuddy/nav-dropdown.html:136 core/models.py:100
#: core/models.py:192 core/models.py:248 core/models.py:305 core/models.py:334 #: core/models.py:134 core/models.py:190 core/models.py:248 core/models.py:276
#: core/models.py:386 core/models.py:417 core/models.py:510 core/models.py:550 #: core/models.py:328 core/models.py:359 core/models.py:452 core/models.py:492
#: core/models.py:577 core/models.py:604 core/models.py:630 #: core/models.py:519 core/models.py:546 core/models.py:572
#: core/templates/core/bmi_list.html:27 #: core/templates/core/bmi_list.html:27
#: core/templates/core/diaperchange_list.html:27 #: core/templates/core/diaperchange_list.html:27
#: core/templates/core/feeding_list.html:27 #: core/templates/core/feeding_list.html:27
@ -334,9 +326,9 @@ msgstr "Kinder"
msgid "Child" msgid "Child"
msgstr "Kind" msgstr "Kind"
#: babybuddy/templates/babybuddy/nav-dropdown.html:144 core/models.py:209 #: babybuddy/templates/babybuddy/nav-dropdown.html:144 core/models.py:151
#: core/models.py:278 core/models.py:319 core/models.py:342 core/models.py:392 #: core/models.py:220 core/models.py:261 core/models.py:284 core/models.py:334
#: core/models.py:554 core/models.py:581 core/models.py:610 core/models.py:634 #: core/models.py:496 core/models.py:523 core/models.py:552 core/models.py:576
#: core/templates/core/note_confirm_delete.html:7 #: core/templates/core/note_confirm_delete.html:7
#: core/templates/core/note_form.html:13 core/templates/core/note_list.html:4 #: core/templates/core/note_form.html:13 core/templates/core/note_list.html:4
#: core/templates/core/note_list.html:7 core/templates/core/note_list.html:12 #: core/templates/core/note_list.html:7 core/templates/core/note_list.html:12
@ -345,7 +337,7 @@ msgstr "Notizen"
#: babybuddy/templates/babybuddy/nav-dropdown.html:164 #: babybuddy/templates/babybuddy/nav-dropdown.html:164
msgid "Measurements" msgid "Measurements"
msgstr "Messungen" msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:178 #: babybuddy/templates/babybuddy/nav-dropdown.html:178
msgid "Temperature reading" msgid "Temperature reading"
@ -355,8 +347,8 @@ msgstr "Temperatur Messung"
msgid "Weight entry" msgid "Weight entry"
msgstr "Gewichtseintrag" msgstr "Gewichtseintrag"
#: babybuddy/templates/babybuddy/nav-dropdown.html:200 core/models.py:579 #: babybuddy/templates/babybuddy/nav-dropdown.html:200 core/models.py:521
#: core/models.py:588 core/models.py:589 core/models.py:592 #: core/models.py:530 core/models.py:531 core/models.py:534
#: core/templates/core/height_confirm_delete.html:7 #: core/templates/core/height_confirm_delete.html:7
#: core/templates/core/height_form.html:13 #: core/templates/core/height_form.html:13
#: core/templates/core/height_list.html:4 #: core/templates/core/height_list.html:4
@ -378,8 +370,8 @@ msgstr "Gewicht"
msgid "Height entry" msgid "Height entry"
msgstr "Gewichtseintrag" msgstr "Gewichtseintrag"
#: babybuddy/templates/babybuddy/nav-dropdown.html:214 core/models.py:607 #: babybuddy/templates/babybuddy/nav-dropdown.html:214 core/models.py:549
#: core/models.py:617 core/models.py:618 core/models.py:621 #: core/models.py:559 core/models.py:560 core/models.py:563
#: core/templates/core/head_circumference_confirm_delete.html:7 #: core/templates/core/head_circumference_confirm_delete.html:7
#: core/templates/core/head_circumference_form.html:13 #: core/templates/core/head_circumference_form.html:13
#: core/templates/core/head_circumference_list.html:4 #: core/templates/core/head_circumference_list.html:4
@ -398,8 +390,8 @@ msgstr ""
msgid "Head Circumference entry" msgid "Head Circumference entry"
msgstr "" msgstr ""
#: babybuddy/templates/babybuddy/nav-dropdown.html:228 core/models.py:632 #: babybuddy/templates/babybuddy/nav-dropdown.html:228 core/models.py:574
#: core/models.py:641 core/models.py:642 core/models.py:645 #: core/models.py:583 core/models.py:584 core/models.py:587
#: core/templates/core/bmi_confirm_delete.html:7 #: core/templates/core/bmi_confirm_delete.html:7
#: core/templates/core/bmi_form.html:13 core/templates/core/bmi_list.html:4 #: core/templates/core/bmi_form.html:13 core/templates/core/bmi_list.html:4
#: core/templates/core/bmi_list.html:7 core/templates/core/bmi_list.html:12 #: core/templates/core/bmi_list.html:7 core/templates/core/bmi_list.html:12
@ -431,7 +423,7 @@ msgid "Change"
msgstr "Wechsel" msgstr "Wechsel"
#: babybuddy/templates/babybuddy/nav-dropdown.html:268 #: babybuddy/templates/babybuddy/nav-dropdown.html:268
#: babybuddy/templates/babybuddy/welcome.html:34 core/models.py:286 #: babybuddy/templates/babybuddy/welcome.html:34 core/models.py:228
#: core/templates/core/feeding_confirm_delete.html:7 #: core/templates/core/feeding_confirm_delete.html:7
#: core/templates/core/feeding_form.html:13 #: core/templates/core/feeding_form.html:13
#: core/templates/core/feeding_list.html:4 #: core/templates/core/feeding_list.html:4
@ -451,7 +443,7 @@ msgstr "Bauchzeit-Eintrag"
#: babybuddy/templates/babybuddy/nav-dropdown.html:325 #: babybuddy/templates/babybuddy/nav-dropdown.html:325
#: babybuddy/templates/babybuddy/user_list.html:17 #: babybuddy/templates/babybuddy/user_list.html:17
#: babybuddy/templates/babybuddy/user_password_form.html:7 #: babybuddy/templates/babybuddy/user_password_form.html:7
#: babybuddy/templates/babybuddy/user_settings_form.html:7 core/models.py:436 #: babybuddy/templates/babybuddy/user_settings_form.html:7 core/models.py:378
#: core/templates/core/timer_list.html:32 #: core/templates/core/timer_list.html:32
msgid "User" msgid "User"
msgstr "Benutzer" msgstr "Benutzer"
@ -639,7 +631,7 @@ msgstr "E-Mail"
msgid "Staff" msgid "Staff"
msgstr "Angestellte" msgstr "Angestellte"
#: babybuddy/templates/babybuddy/user_list.html:22 core/models.py:431 #: babybuddy/templates/babybuddy/user_list.html:22 core/models.py:373
#: core/templates/core/timer_list.html:31 #: core/templates/core/timer_list.html:31
msgid "Active" msgid "Active"
msgstr "Aktiv" msgstr "Aktiv"
@ -716,7 +708,9 @@ msgstr ""
"Lerne und sehe die Bedürfnisse deines Babys voraus, ohne (<em>allzu viel</" "Lerne und sehe die Bedürfnisse deines Babys voraus, ohne (<em>allzu viel</"
"em>)Spekulation indem du Baby Buddy verwendest &mdash;" "em>)Spekulation indem du Baby Buddy verwendest &mdash;"
#: babybuddy/templates/babybuddy/welcome.html:26 core/models.py:217 ==== BASE ====
#: babybuddy/templates/babybuddy/welcome.html:26 core/models.py:166
==== BASE ====
#: core/templates/core/diaperchange_confirm_delete.html:7 #: core/templates/core/diaperchange_confirm_delete.html:7
#: core/templates/core/diaperchange_form.html:13 #: core/templates/core/diaperchange_form.html:13
#: core/templates/core/diaperchange_list.html:4 #: core/templates/core/diaperchange_list.html:4
@ -748,6 +742,52 @@ msgstr ""
msgid "Add a Child" msgid "Add a Child"
msgstr "Kind hinzufügen" msgstr "Kind hinzufügen"
#: babybuddy/templates/error/400.html:4 babybuddy/templates/error/400.html:7
msgid "Bad Request"
msgstr ""
#: babybuddy/templates/error/403.html:4 babybuddy/templates/error/403.html:7
msgid "Permission Denied"
msgstr "Zugriff verweigert"
#: babybuddy/templates/error/403.html:9
msgid ""
"You do not have permission to access this resource. Contact a site "
"administrator for assistance."
msgstr ""
"Du hast keine Berechtigung auf diese Ressource zuzugreifen. Für "
"Unterstützung kontaktiere bitte den Administrator."
#: babybuddy/templates/error/403_csrf_bad_origin.html:14
msgid "How to Fix"
msgstr ""
#: babybuddy/templates/error/403_csrf_bad_origin.html:15
#, python-format
msgid ""
"Add <samp>%(origin)s</samp> to the <code>CSRF_TRUSTED_ORIGINS</code> "
"environment variable. If multiple origins are required separate with commas."
msgstr ""
#: babybuddy/templates/error/404.html:4 babybuddy/templates/error/404.html:7
msgid "Page Not Found"
msgstr ""
#: babybuddy/templates/error/404.html:9
#, python-format
msgid "The path <code>%(request_path)s</code> does not exist."
msgstr ""
#: babybuddy/templates/error/500.html:4 babybuddy/templates/error/500.html:7
msgid "Server Error"
msgstr ""
#: babybuddy/templates/error/base.html:14
#, fuzzy
#| msgid "Welcome to Baby Buddy!"
msgid "Return to Baby Buddy"
msgstr "Willkommen bei Baby Buddy!"
#: babybuddy/templates/registration/login.html:32 #: babybuddy/templates/registration/login.html:32
msgid "Login" msgid "Login"
msgstr "Login" msgstr "Login"
@ -821,34 +861,42 @@ msgstr ""
"Bitte gib deine Account E-Mail-Adresse ins folgende Formular ein. Wenn die " "Bitte gib deine Account E-Mail-Adresse ins folgende Formular ein. Wenn die "
"Adresse gültig ist, erhältst du Anweisungen um das Passwort zurückzusetzen." "Adresse gültig ist, erhältst du Anweisungen um das Passwort zurückzusetzen."
#: babybuddy/views.py:76 #: babybuddy/views.py:43
msgid "Forbidden"
msgstr ""
#: babybuddy/views.py:44
msgid "CSRF verification failed. Request aborted."
msgstr ""
#: babybuddy/views.py:102
#, python-format #, python-format
msgid "User %(username)s added!" msgid "User %(username)s added!"
msgstr "User %(username)s hinzugefügt!" msgstr "User %(username)s hinzugefügt!"
#: babybuddy/views.py:87 #: babybuddy/views.py:113
#, python-format #, python-format
msgid "User %(username)s updated." msgid "User %(username)s updated."
msgstr "User %(username)s geändert!" msgstr "User %(username)s geändert!"
#: babybuddy/views.py:99 #: babybuddy/views.py:125
#, python-brace-format #, python-brace-format
msgid "User {user} deleted." msgid "User {user} deleted."
msgstr "User {user} gelöscht." msgstr "User {user} gelöscht."
#: babybuddy/views.py:120 #: babybuddy/views.py:146
msgid "Password updated." msgid "Password updated."
msgstr "Passwort geändert." msgstr "Passwort geändert."
#: babybuddy/views.py:149 #: babybuddy/views.py:175
msgid "User API key regenerated." msgid "User API key regenerated."
msgstr "User API-Key neu generiert." msgstr "User API-Key neu generiert."
#: babybuddy/views.py:162 #: babybuddy/views.py:188
msgid "Settings saved!" msgid "Settings saved!"
msgstr "Einstellungen gespeichert!" msgstr "Einstellungen gespeichert!"
#: core/forms.py:116 #: core/forms.py:115
msgid "Name does not match child name." msgid "Name does not match child name."
msgstr "Name entspricht nicht dem Kindernamen." msgstr "Name entspricht nicht dem Kindernamen."
@ -868,23 +916,15 @@ msgstr "Dauer zu lange."
msgid "Another entry intersects the specified time period." msgid "Another entry intersects the specified time period."
msgstr "Ein anderer Eintrag schneidet sich mit der angegebenen Zeitperiode." msgstr "Ein anderer Eintrag schneidet sich mit der angegebenen Zeitperiode."
#: core/models.py:81 #: core/models.py:70
msgid "Date/time can not be in the future." msgid "Date/time can not be in the future."
msgstr "Datum/Zeit darf nicht in der Zukunft liegen." msgstr "Datum/Zeit darf nicht in der Zukunft liegen."
#: core/models.py:95 #: core/models.py:76
msgid "Tag"
msgstr "Tag"
#: core/models.py:96
msgid "Tags"
msgstr "Tags"
#: core/models.py:134
msgid "First name" msgid "First name"
msgstr "Vorname" msgstr "Vorname"
#: core/models.py:136 #: core/models.py:78
msgid "Last name" msgid "Last name"
msgstr "Nachname" msgstr "Nachname"
@ -892,69 +932,69 @@ msgstr "Nachname"
msgid "Birth date" msgid "Birth date"
msgstr "Geburtsdatum" msgstr "Geburtsdatum"
#: core/models.py:145 #: core/models.py:87
msgid "Slug" msgid "Slug"
msgstr "Slug" msgstr "Slug"
#: core/models.py:148 #: core/models.py:90
msgid "Picture" msgid "Picture"
msgstr "Bild" msgstr "Bild"
#: core/models.py:194 core/models.py:309 core/models.py:391 #: core/models.py:136 core/models.py:252 core/models.py:333
#: core/templates/core/diaperchange_list.html:25 #: core/templates/core/diaperchange_list.html:25
#: core/templates/core/note_list.html:25 #: core/templates/core/note_list.html:25
#: core/templates/core/temperature_list.html:25 #: core/templates/core/temperature_list.html:25
msgid "Time" msgid "Time"
msgstr "Zeit" msgstr "Zeit"
#: core/models.py:195 core/templates/core/diaperchange_list.html:60 #: core/models.py:137 core/templates/core/diaperchange_list.html:60
#: reports/graphs/diaperchange_types.py:36 #: reports/graphs/diaperchange_types.py:36
msgid "Wet" msgid "Wet"
msgstr "Nass" msgstr "Nass"
#: core/models.py:196 core/templates/core/diaperchange_list.html:61 #: core/models.py:138 core/templates/core/diaperchange_list.html:61
#: reports/graphs/diaperchange_types.py:30 #: reports/graphs/diaperchange_types.py:30
msgid "Solid" msgid "Solid"
msgstr "Fest" msgstr "Fest"
#: core/models.py:200 #: core/models.py:142
msgid "Black" msgid "Black"
msgstr "Schwarz" msgstr "Schwarz"
#: core/models.py:201 #: core/models.py:143
msgid "Brown" msgid "Brown"
msgstr "Braun" msgstr "Braun"
#: core/models.py:202 #: core/models.py:144
msgid "Green" msgid "Green"
msgstr "Grün" msgstr "Grün"
#: core/models.py:203 #: core/models.py:145
msgid "Yellow" msgid "Yellow"
msgstr "Gelb" msgstr "Gelb"
#: core/models.py:206 core/templates/core/diaperchange_list.html:30 #: core/models.py:148 core/templates/core/diaperchange_list.html:30
msgid "Color" msgid "Color"
msgstr "Farbe" msgstr "Farbe"
#: core/models.py:208 core/models.py:277 #: core/models.py:150 core/models.py:219
#: core/templates/core/diaperchange_list.html:31 #: core/templates/core/diaperchange_list.html:31
msgid "Amount" msgid "Amount"
msgstr "Menge" msgstr "Menge"
#: core/models.py:238 #: core/models.py:180
msgid "Wet and/or solid is required." msgid "Wet and/or solid is required."
msgstr "Nass und/oder fest wird benötigt." msgstr "Nass und/oder fest wird benötigt."
#: core/models.py:250 core/models.py:337 core/models.py:423 core/models.py:512 #: core/models.py:192 core/models.py:279 core/models.py:365 core/models.py:454
msgid "Start time" msgid "Start time"
msgstr "Startzeit" msgstr "Startzeit"
#: core/models.py:251 core/models.py:338 core/models.py:426 core/models.py:513 #: core/models.py:193 core/models.py:280 core/models.py:368 core/models.py:455
msgid "End time" msgid "End time"
msgstr "Endzeit" msgstr "Endzeit"
#: core/models.py:253 core/models.py:340 core/models.py:429 core/models.py:515 #: core/models.py:195 core/models.py:282 core/models.py:371 core/models.py:457
#: core/templates/core/feeding_list.html:34 #: core/templates/core/feeding_list.html:34
#: core/templates/core/sleep_list.html:30 #: core/templates/core/sleep_list.html:30
#: core/templates/core/timer_list.html:29 #: core/templates/core/timer_list.html:29
@ -962,67 +1002,67 @@ msgstr "Endzeit"
msgid "Duration" msgid "Duration"
msgstr "Dauer" msgstr "Dauer"
#: core/models.py:257 #: core/models.py:199
msgid "Breast milk" msgid "Breast milk"
msgstr "Brustmilch" msgstr "Brustmilch"
#: core/models.py:258 #: core/models.py:200
msgid "Formula" msgid "Formula"
msgstr "Formel" msgstr "Formel"
#: core/models.py:259 #: core/models.py:201
msgid "Fortified breast milk" msgid "Fortified breast milk"
msgstr "Angereicherte Brustmilch" msgstr "Angereicherte Brustmilch"
#: core/models.py:260 #: core/models.py:202
msgid "Solid food" msgid "Solid food"
msgstr "" msgstr ""
#: core/models.py:263 core/templates/core/feeding_list.html:30 #: core/models.py:205 core/templates/core/feeding_list.html:30
msgid "Type" msgid "Type"
msgstr "Typ" msgstr "Typ"
#: core/models.py:267 #: core/models.py:209
msgid "Bottle" msgid "Bottle"
msgstr "Fläschchen" msgstr "Fläschchen"
#: core/models.py:268 #: core/models.py:210
msgid "Left breast" msgid "Left breast"
msgstr "Linke Brust" msgstr "Linke Brust"
#: core/models.py:269 #: core/models.py:211
msgid "Right breast" msgid "Right breast"
msgstr "Rechte Brust" msgstr "Rechte Brust"
#: core/models.py:270 #: core/models.py:212
msgid "Both breasts" msgid "Both breasts"
msgstr "Beide Brüste" msgstr "Beide Brüste"
#: core/models.py:271 #: core/models.py:213
msgid "Parent fed" msgid "Parent fed"
msgstr "" msgstr ""
#: core/models.py:272 #: core/models.py:214
msgid "Self fed" msgid "Self fed"
msgstr "" msgstr ""
#: core/models.py:275 core/templates/core/feeding_list.html:29 #: core/models.py:217 core/templates/core/feeding_list.html:29
msgid "Method" msgid "Method"
msgstr "Methode" msgstr "Methode"
#: core/models.py:336 #: core/models.py:278
msgid "Napping" msgid "Napping"
msgstr "" msgstr ""
#: core/models.py:420 core/templates/core/timer_list.html:25 #: core/models.py:362 core/templates/core/timer_list.html:25
msgid "Name" msgid "Name"
msgstr "Name" msgstr "Name"
#: core/models.py:444 core/templates/core/timer_form.html:4 #: core/models.py:386 core/templates/core/timer_form.html:4
msgid "Timer" msgid "Timer"
msgstr "Timer" msgstr "Timer"
#: core/models.py:445 core/templates/core/timer_confirm_delete.html:9 #: core/models.py:387 core/templates/core/timer_confirm_delete.html:9
#: core/templates/core/timer_confirm_delete_inactive.html:9 #: core/templates/core/timer_confirm_delete_inactive.html:9
#: core/templates/core/timer_detail.html:8 #: core/templates/core/timer_detail.html:8
#: core/templates/core/timer_form.html:7 core/templates/core/timer_list.html:4 #: core/templates/core/timer_form.html:7 core/templates/core/timer_list.html:4
@ -1031,16 +1071,16 @@ msgstr "Timer"
msgid "Timers" msgid "Timers"
msgstr "Timer" msgstr "Timer"
#: core/models.py:448 #: core/models.py:390
#, python-brace-format #, python-brace-format
msgid "Timer #{id}" msgid "Timer #{id}"
msgstr "Timer #{id}" msgstr "Timer #{id}"
#: core/models.py:518 core/templates/core/tummytime_list.html:30 #: core/models.py:460 core/templates/core/tummytime_list.html:30
msgid "Milestone" msgid "Milestone"
msgstr "Meilenstein" msgstr "Meilenstein"
#: core/models.py:553 core/models.py:580 core/models.py:609 core/models.py:633 #: core/models.py:495 core/models.py:522 core/models.py:551 core/models.py:575
#: core/templates/core/bmi_list.html:25 #: core/templates/core/bmi_list.html:25
#: core/templates/core/feeding_list.html:25 #: core/templates/core/feeding_list.html:25
#: core/templates/core/head_circumference_list.html:25 #: core/templates/core/head_circumference_list.html:25
@ -1959,3 +1999,6 @@ msgstr "Es gibt nicht genügend Daten um diesen Report zu generieren."
#: reports/templates/reports/tummytime_duration.html:8 #: reports/templates/reports/tummytime_duration.html:8
msgid "Total Tummy Time Durations" msgid "Total Tummy Time Durations"
msgstr "" msgstr ""
#~ msgid "Total feeding amount"
#~ msgstr "Total Mahlzeiten"

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -1,18 +1,30 @@
extra_css: extra_css:
- css/extras.css - css/extras.css
markdown_extensions:
- pymdownx.highlight
- pymdownx.inlinehilite
- pymdownx.snippets
- pymdownx.superfences
nav: nav:
- 'index.md' - 'index.md'
- 'Setup': - 'Setup':
- 'setup/deployment.md' - 'setup/deployment.md'
- 'setup/configuration.md' - 'setup/configuration.md'
- 'setup/ssl.md'
- 'setup/subdirectory.md'
- 'setup/proxy.md'
- 'User Guide': - 'User Guide':
- 'user-guide/getting-started.md' - 'user-guide/getting-started.md'
- 'user-guide/managing-users.md' - 'user-guide/managing-users.md'
- 'user-guide/adding-entries.md' - 'user-guide/adding-entries.md'
- 'user-guide/using-timers.md' - 'user-guide/using-timers.md'
- 'Contributing':
- 'contributing/development-environment.md'
- 'contributing/translation.md'
- 'contributing/pull-requests.md'
- 'contributing/gulp-command-reference.md'
- 'import-export.md' - 'import-export.md'
- 'api.md' - 'api.md'
- 'contributing.md'
theme: theme:
name: material name: material
favicon: assets/images/favicon.svg favicon: assets/images/favicon.svg

13
package-lock.json generated
View File

@ -14,6 +14,7 @@
"gulp-csso": "^4.0.1", "gulp-csso": "^4.0.1",
"gulp-flatten": "^0.4.0", "gulp-flatten": "^0.4.0",
"gulp-fontello": "^0.5.2", "gulp-fontello": "^0.5.2",
"gulp-remove-sourcemaps": "^1.0.4",
"gulp-sass": "^5.0.0", "gulp-sass": "^5.0.0",
"gulp-sass-glob": "^1.1.0", "gulp-sass-glob": "^1.1.0",
"gulp-spawn": "^1.0.0", "gulp-spawn": "^1.0.0",
@ -3858,6 +3859,12 @@
"y18n": "^3.2.0" "y18n": "^3.2.0"
} }
}, },
"node_modules/gulp-remove-sourcemaps": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/gulp-remove-sourcemaps/-/gulp-remove-sourcemaps-1.0.4.tgz",
"integrity": "sha512-zW+YRzgbJIDDqAlIa/EtFJbOV1GSzQe2qV2zy+PjI1TJ27JCykQ5eYC+ajogEKZUOzoosgBnXzUGQlVW3YQUIw==",
"dev": true
},
"node_modules/gulp-sass": { "node_modules/gulp-sass": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-5.0.0.tgz", "resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-5.0.0.tgz",
@ -13127,6 +13134,12 @@
} }
} }
}, },
"gulp-remove-sourcemaps": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/gulp-remove-sourcemaps/-/gulp-remove-sourcemaps-1.0.4.tgz",
"integrity": "sha512-zW+YRzgbJIDDqAlIa/EtFJbOV1GSzQe2qV2zy+PjI1TJ27JCykQ5eYC+ajogEKZUOzoosgBnXzUGQlVW3YQUIw==",
"dev": true
},
"gulp-sass": { "gulp-sass": {
"version": "5.0.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-5.0.0.tgz", "resolved": "https://registry.npmjs.org/gulp-sass/-/gulp-sass-5.0.0.tgz",

View File

@ -14,6 +14,7 @@
"gulp-csso": "^4.0.1", "gulp-csso": "^4.0.1",
"gulp-flatten": "^0.4.0", "gulp-flatten": "^0.4.0",
"gulp-fontello": "^0.5.2", "gulp-fontello": "^0.5.2",
"gulp-remove-sourcemaps": "^1.0.4",
"gulp-sass": "^5.0.0", "gulp-sass": "^5.0.0",
"gulp-sass-glob": "^1.1.0", "gulp-sass-glob": "^1.1.0",
"gulp-spawn": "^1.0.0", "gulp-spawn": "^1.0.0",

View File

@ -0,0 +1,10 @@
{% extends 'babybuddy/page.html' %}
{% load i18n static %}
{% block title %}{% endblock %}
{% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li>
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:child' object.slug %}">{{ object }}</a></li>
<li class="breadcrumb-item"><a href="{% url 'reports:report-list' object.slug %}">{% trans "Reports" %}</a></li>
{% endblock %}

View File

@ -1,12 +1,10 @@
{% extends 'babybuddy/page.html' %} {% extends 'reports/base.html' %}
{% load i18n static %} {% load i18n static %}
{% block title %}{% endblock %} {% block title %}{% endblock %}
{% block breadcrumbs %} {% block breadcrumbs %}
<li class="breadcrumb-item"><a href="{% url 'core:child-list' %}">{% trans "Children" %}</a></li> {{ block.super }}
<li class="breadcrumb-item font-weight-bold"><a href="{% url 'core:child' object.slug %}">{{ object }}</a></li>
<li class="breadcrumb-item">{% trans "Reports" %}</li>
{% endblock %} {% endblock %}
{% block content %} {% block content %}

View File

@ -0,0 +1,24 @@
{% extends 'reports/base.html' %}
{% load i18n static %}
{% block title %}{% trans "Reports" %} - {{ object }}{% endblock %}
{% block content %}
<div class="container-fluid">
<h1>Reports</h1>
<div class="list-group">
<a href="{% url 'reports:report-bmi-bmi-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Body Mass Index (BMI)" %}</a>
<a href="{% url 'reports:report-diaperchange-amounts-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Diaper Change Amounts" %}</a>
<a href="{% url 'reports:report-diaperchange-types-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Diaper Change Types" %}</a>
<a href="{% url 'reports:report-diaperchange-lifetimes-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Diaper Lifetimes" %}</a>
<a href="{% url 'reports:report-feeding-amounts-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Feeding Amounts" %}</a>
<a href="{% url 'reports:report-feeding-duration-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Feeding Durations (Average)" %}</a>
<a href="{% url 'reports:report-head-circumference-head-circumference-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Head Circumference" %}</a>
<a href="{% url 'reports:report-height-height-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Height" %}</a>
<a href="{% url 'reports:report-sleep-pattern-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Sleep Pattern" %}</a>
<a href="{% url 'reports:report-sleep-totals-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Sleep Totals" %}</a>
<a href="{% url 'reports:report-tummytime-duration-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Tummy Time Durations (Sum)" %}</a>
<a href="{% url 'reports:report-weight-weight-child' object.slug %}" class="list-group-item list-group-item-action">{% trans "Weight" %}</a>
</div>
</div>
{% endblock %}

View File

@ -32,6 +32,9 @@ class ViewsTestCase(TestCase):
child = models.Child.objects.first() child = models.Child.objects.first()
base_url = "/children/{}/reports".format(child.slug) base_url = "/children/{}/reports".format(child.slug)
page = self.c.get(base_url)
self.assertEqual(page.status_code, 200)
page = self.c.get("{}/changes/amounts/".format(base_url)) page = self.c.get("{}/changes/amounts/".format(base_url))
self.assertEqual(page.status_code, 200) self.assertEqual(page.status_code, 200)
page = self.c.get("{}/changes/lifetimes/".format(base_url)) page = self.c.get("{}/changes/lifetimes/".format(base_url))

View File

@ -6,6 +6,11 @@ from . import views
app_name = "reports" app_name = "reports"
urlpatterns = [ urlpatterns = [
path(
"children/<str:slug>/reports",
views.ChildReportList.as_view(),
name="report-list",
),
path( path(
"children/<str:slug>/reports/changes/amounts/", "children/<str:slug>/reports/changes/amounts/",
views.DiaperChangeAmounts.as_view(), views.DiaperChangeAmounts.as_view(),

View File

@ -7,6 +7,16 @@ from core import models
from . import graphs from . import graphs
class ChildReportList(PermissionRequiredMixin, DetailView):
"""
Listing of available reports for a child.
"""
model = models.Child
permission_required = ("core.view_child",)
template_name = "reports/report_list.html"
class DiaperChangeAmounts(PermissionRequiredMixin, DetailView): class DiaperChangeAmounts(PermissionRequiredMixin, DetailView):
""" """
Graph of diaper "amounts" - measurements of urine output. Graph of diaper "amounts" - measurements of urine output.
@ -109,29 +119,6 @@ class FeedingDurationChildReport(PermissionRequiredMixin, DetailView):
return context return context
class TummyTimeDurationChildReport(PermissionRequiredMixin, DetailView):
"""
Graph of tummy time durations over time.
"""
model = models.Child
permission_required = ("core.view_child",)
template_name = "reports/tummytime_duration.html"
def __init__(self):
super(TummyTimeDurationChildReport, self).__init__()
self.html = ""
self.js = ""
def get_context_data(self, **kwargs):
context = super(TummyTimeDurationChildReport, self).get_context_data(**kwargs)
child = context["object"]
instances = models.TummyTime.objects.filter(child=child)
if instances:
context["html"], context["js"] = graphs.tummytime_duration(instances)
return context
class SleepPatternChildReport(PermissionRequiredMixin, DetailView): class SleepPatternChildReport(PermissionRequiredMixin, DetailView):
""" """
Graph of sleep pattern comparing sleep to wake times by day. Graph of sleep pattern comparing sleep to wake times by day.
@ -178,6 +165,29 @@ class SleepTotalsChildReport(PermissionRequiredMixin, DetailView):
return context return context
class TummyTimeDurationChildReport(PermissionRequiredMixin, DetailView):
"""
Graph of tummy time durations over time.
"""
model = models.Child
permission_required = ("core.view_child",)
template_name = "reports/tummytime_duration.html"
def __init__(self):
super(TummyTimeDurationChildReport, self).__init__()
self.html = ""
self.js = ""
def get_context_data(self, **kwargs):
context = super(TummyTimeDurationChildReport, self).get_context_data(**kwargs)
child = context["object"]
instances = models.TummyTime.objects.filter(child=child)
if instances:
context["html"], context["js"] = graphs.tummytime_duration(instances)
return context
class WeightWeightChildReport(PermissionRequiredMixin, DetailView): class WeightWeightChildReport(PermissionRequiredMixin, DetailView):
""" """
Graph of weight change over time. Graph of weight change over time.

Binary file not shown.

View File

@ -125,6 +125,10 @@
margin-right: 4px; margin-right: 4px;
} }
#changelist-search .help {
word-break: break-word;
}
/* FILTER COLUMN */ /* FILTER COLUMN */
#changelist-filter { #changelist-filter {

Binary file not shown.

View File

@ -125,6 +125,10 @@
margin-right: 4px; margin-right: 4px;
} }
#changelist-search .help {
word-break: break-word;
}
/* FILTER COLUMN */ /* FILTER COLUMN */
#changelist-filter { #changelist-filter {

Binary file not shown.

Binary file not shown.

View File

@ -37,16 +37,19 @@ label {
/* RADIO BUTTONS */ /* RADIO BUTTONS */
form ul.radiolist li { form div.radiolist div {
list-style-type: none; padding-right: 7px;
} }
form ul.radiolist label { form div.radiolist.inline div {
float: none; display: inline-block;
display: inline;
} }
form ul.radiolist input[type="radio"] { form div.radiolist label {
width: auto;
}
form div.radiolist input[type="radio"] {
margin: -2px 4px 0 0; margin: -2px 4px 0 0;
padding: 0; padding: 0;
} }
@ -84,6 +87,7 @@ form ul.inline li {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
margin-left: 170px; margin-left: 170px;
overflow-wrap: break-word;
} }
.aligned ul label { .aligned ul label {
@ -105,7 +109,7 @@ form .aligned ul {
padding-left: 10px; padding-left: 10px;
} }
form .aligned ul.radiolist { form .aligned div.radiolist {
display: inline-block; display: inline-block;
margin: 0; margin: 0;
padding: 0; padding: 0;

BIN
static/admin/css/forms.332ab41432e2.css.gz generated Normal file

Binary file not shown.

View File

@ -37,16 +37,19 @@ label {
/* RADIO BUTTONS */ /* RADIO BUTTONS */
form ul.radiolist li { form div.radiolist div {
list-style-type: none; padding-right: 7px;
} }
form ul.radiolist label { form div.radiolist.inline div {
float: none; display: inline-block;
display: inline;
} }
form ul.radiolist input[type="radio"] { form div.radiolist label {
width: auto;
}
form div.radiolist input[type="radio"] {
margin: -2px 4px 0 0; margin: -2px 4px 0 0;
padding: 0; padding: 0;
} }
@ -84,6 +87,7 @@ form ul.inline li {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
margin-left: 170px; margin-left: 170px;
overflow-wrap: break-word;
} }
.aligned ul label { .aligned ul label {
@ -105,7 +109,7 @@ form .aligned ul {
padding-left: 10px; padding-left: 10px;
} }
form .aligned ul.radiolist { form .aligned div.radiolist {
display: inline-block; display: inline-block;
margin: 0; margin: 0;
padding: 0; padding: 0;

Binary file not shown.

View File

@ -13,6 +13,7 @@
.login #header h1 { .login #header h1 {
font-size: 18px; font-size: 18px;
margin: 0;
} }
.login #header h1 a { .login #header h1 a {

BIN
static/admin/css/login.8b76a9f7cbf6.css.gz generated Normal file

Binary file not shown.

Binary file not shown.

View File

@ -13,6 +13,7 @@
.login #header h1 { .login #header h1 {
font-size: 18px; font-size: 18px;
margin: 0;
} }
.login #header h1 a { .login #header h1 a {

Binary file not shown.

Binary file not shown.

View File

@ -60,13 +60,10 @@
} }
.main.shifted > #nav-sidebar { .main.shifted > #nav-sidebar {
left: 24px;
margin-left: 0; margin-left: 0;
} }
[dir="rtl"] .main.shifted > #nav-sidebar { [dir="rtl"] .main.shifted > #nav-sidebar {
left: 0;
right: 24px;
margin-right: 0; margin-right: 0;
} }
@ -118,3 +115,25 @@
max-width: 100%; max-width: 100%;
} }
} }
#nav-filter {
width: 100%;
box-sizing: border-box;
padding: 2px 5px;
margin: 5px 0;
border: 1px solid var(--border-color);
background-color: var(--darkened-bg);
color: var(--body-fg);
}
#nav-filter:focus {
border-color: var(--body-quiet-color);
}
#nav-filter.no-results {
background: var(--message-error-bg);
}
#nav-sidebar table {
width: 100%;
}

Binary file not shown.

View File

@ -60,13 +60,10 @@
} }
.main.shifted > #nav-sidebar { .main.shifted > #nav-sidebar {
left: 24px;
margin-left: 0; margin-left: 0;
} }
[dir="rtl"] .main.shifted > #nav-sidebar { [dir="rtl"] .main.shifted > #nav-sidebar {
left: 0;
right: 24px;
margin-right: 0; margin-right: 0;
} }
@ -118,3 +115,25 @@
max-width: 100%; max-width: 100%;
} }
} }
#nav-filter {
width: 100%;
box-sizing: border-box;
padding: 2px 5px;
margin: 5px 0;
border: 1px solid var(--border-color);
background-color: var(--darkened-bg);
color: var(--body-fg);
}
#nav-filter:focus {
border-color: var(--body-quiet-color);
}
#nav-filter.no-results {
background: var(--message-error-bg);
}
#nav-sidebar table {
width: 100%;
}

Binary file not shown.

Binary file not shown.

View File

@ -232,7 +232,7 @@ input[type="submit"], button {
margin-left: 15px; margin-left: 15px;
} }
form .aligned ul.radiolist { form .aligned div.radiolist {
margin-left: 2px; margin-left: 2px;
} }
@ -650,12 +650,13 @@ input[type="submit"], button {
padding-left: 0; padding-left: 0;
} }
form .aligned ul.radiolist { form .aligned div.radiolist {
margin-top: 5px;
margin-right: 15px; margin-right: 15px;
margin-bottom: -3px; margin-bottom: -3px;
} }
form .aligned ul.radiolist:not(.inline) li + li { form .aligned div.radiolist:not(.inline) div + div {
margin-top: 5px; margin-top: 5px;
} }

Binary file not shown.

View File

@ -232,7 +232,7 @@ input[type="submit"], button {
margin-left: 15px; margin-left: 15px;
} }
form .aligned ul.radiolist { form .aligned div.radiolist {
margin-left: 2px; margin-left: 2px;
} }
@ -650,12 +650,13 @@ input[type="submit"], button {
padding-left: 0; padding-left: 0;
} }
form .aligned ul.radiolist { form .aligned div.radiolist {
margin-top: 5px;
margin-right: 15px; margin-right: 15px;
margin-bottom: -3px; margin-bottom: -3px;
} }
form .aligned ul.radiolist:not(.inline) li + li { form .aligned div.radiolist:not(.inline) div + div {
margin-top: 5px; margin-top: 5px;
} }

Binary file not shown.

Binary file not shown.

View File

@ -156,7 +156,7 @@
}); });
}); });
document.querySelector('#changelist-form button[name=index]').addEventListener('click', function() { document.querySelector('#changelist-form button[name=index]').addEventListener('click', function(event) {
if (list_editable_changed) { if (list_editable_changed) {
const confirmed = confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost.")); const confirmed = confirm(gettext("You have unsaved changes on individual editable fields. If you run an action, your unsaved changes will be lost."));
if (!confirmed) { if (!confirmed) {

BIN
static/admin/js/actions.eac7e3441574.js.gz generated Normal file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More