Compare commits

...

40 Commits

Author SHA1 Message Date
vabene1111
851853740d added catalan language chooser 2020-12-15 22:19:36 +01:00
vabene1111
7ca88f3c0a fixed scaling for fractions 2020-12-15 22:16:15 +01:00
vabene1111
ac2e9dd6cb updated german translation 2020-12-15 22:04:40 +01:00
vabene1111
b2a34ce59a Merge pull request #255 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_de
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'de'
2020-12-15 22:04:12 +01:00
transifex-integration[bot]
6124501f5a Apply translations in de
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'de' language.
2020-12-15 21:03:40 +00:00
vabene1111
4ec313f752 fixed another importer issue 2020-12-15 21:59:34 +01:00
vabene1111
dd07c56ede fixed issue with new fraction importer 2020-12-15 21:52:56 +01:00
vabene1111
77fae46aee translation updates 2020-12-15 21:39:42 +01:00
vabene1111
ff573b0358 Merge pull request #254 from vabene1111/translations_cookbook-locale-en-lc-messages-django-po--develop_ca
Translate '/cookbook/locale/en/LC_MESSAGES/django.po' in 'ca'
2020-12-15 13:02:52 +01:00
transifex-integration[bot]
247eab2a4f Apply translations in ca
translation completed for the source file '/cookbook/locale/en/LC_MESSAGES/django.po'
on the 'ca' language.
2020-12-15 12:00:28 +00:00
vabene1111
9ea90f1c87 fraction import 2020-12-14 19:56:21 +01:00
vabene1111
e7922a7e47 nutirtion fix 2020-12-14 19:12:01 +01:00
vabene1111
d3bc440c83 meal plan quality of life stuff 2020-12-14 17:37:51 +01:00
vabene1111
910b28fe2d meal plan date handling rewrite 2020-12-14 17:11:40 +01:00
vabene1111
89cd8bc2d2 fixed wrong translation 2020-12-14 15:49:32 +01:00
vabene1111
d2e9ad2ae6 requirement still needed 2020-12-14 15:14:55 +01:00
vabene1111
56c9edd328 removed deprecated psycopg2 2020-12-14 15:11:41 +01:00
vabene1111
7732aa7646 added support for fractions 2020-12-14 14:08:11 +01:00
vabene1111
9863447bac small nutrition fixes 2020-12-14 12:56:18 +01:00
vabene1111
53b00cc4c8 Merge pull request #199 from sebimarkgraf/feature/nutrition
Add nutritional values
2020-12-14 12:29:56 +01:00
vabene1111
4f34ec1be8 Merge pull request #235 from vabene1111/dependabot/pip/pillow-8.0.1
Bump pillow from 7.1.2 to 8.0.1
2020-12-14 12:08:48 +01:00
dependabot[bot]
e444ba91f0 Bump pillow from 7.1.2 to 8.0.1
Bumps [pillow](https://github.com/python-pillow/Pillow) from 7.1.2 to 8.0.1.
- [Release notes](https://github.com/python-pillow/Pillow/releases)
- [Changelog](https://github.com/python-pillow/Pillow/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-pillow/Pillow/compare/7.1.2...8.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-14 11:08:35 +00:00
vabene1111
fa3513eb65 Merge pull request #234 from vabene1111/dependabot/pip/webdavclient3-3.14.5
Bump webdavclient3 from 3.14.4 to 3.14.5
2020-12-14 12:08:29 +01:00
vabene1111
d323778f1d Merge pull request #232 from vabene1111/dependabot/pip/django-autocomplete-light-3.8.1
Bump django-autocomplete-light from 3.5.1 to 3.8.1
2020-12-14 12:08:14 +01:00
vabene1111
53cb5afef6 Merge pull request #236 from vabene1111/dependabot/pip/beautifulsoup4-4.9.3
Bump beautifulsoup4 from 4.9.2 to 4.9.3
2020-12-14 12:07:37 +01:00
vabene1111
0349301919 Merge pull request #242 from vabene1111/dependabot/pip/lxml-4.6.2
Bump lxml from 4.5.1 to 4.6.2
2020-12-14 12:07:24 +01:00
vabene1111
a5d2bd75d6 recompiled messages + added timezone setting 2020-12-14 12:03:43 +01:00
vabene1111
26499ad431 Merge pull request #229 from sebimarkgraf/fix/225-random-button
Include random in filtermixin
2020-12-14 11:51:59 +01:00
dependabot[bot]
2eb72953f0 Bump lxml from 4.5.1 to 4.6.2
Bumps [lxml](https://github.com/lxml/lxml) from 4.5.1 to 4.6.2.
- [Release notes](https://github.com/lxml/lxml/releases)
- [Changelog](https://github.com/lxml/lxml/blob/master/CHANGES.txt)
- [Commits](https://github.com/lxml/lxml/compare/lxml-4.5.1...lxml-4.6.2)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-30 07:22:55 +00:00
Sebastian Markgraf
422113a745 Finish editing of nutritional information 2020-11-17 22:27:01 +01:00
dependabot[bot]
e687d0e569 Bump beautifulsoup4 from 4.9.2 to 4.9.3
Bumps [beautifulsoup4](http://www.crummy.com/software/BeautifulSoup/bs4/) from 4.9.2 to 4.9.3.

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-16 07:19:43 +00:00
dependabot[bot]
76c1529ec1 Bump webdavclient3 from 3.14.4 to 3.14.5
Bumps [webdavclient3](https://github.com/ezhov-evgeny/webdav-client-python-3) from 3.14.4 to 3.14.5.
- [Release notes](https://github.com/ezhov-evgeny/webdav-client-python-3/releases)
- [Changelog](https://github.com/ezhov-evgeny/webdav-client-python-3/blob/develop/RELEASE_NOTES.md)
- [Commits](https://github.com/ezhov-evgeny/webdav-client-python-3/compare/v3.14.4...v3.14.5)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-16 07:19:41 +00:00
dependabot[bot]
d30b2b7ec8 Bump django-autocomplete-light from 3.5.1 to 3.8.1
Bumps [django-autocomplete-light](https://github.com/yourlabs/django-autocomplete-light) from 3.5.1 to 3.8.1.
- [Release notes](https://github.com/yourlabs/django-autocomplete-light/releases)
- [Changelog](https://github.com/yourlabs/django-autocomplete-light/blob/master/CHANGELOG)
- [Commits](https://github.com/yourlabs/django-autocomplete-light/compare/3.5.1...3.8.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-16 07:19:35 +00:00
Sebastian Markgraf
c4fbad614e Include random in filtermixin 2020-11-14 20:20:30 +01:00
Sebastian Markgraf
2f038edf8c Fix missing string literals for translations. 2020-11-06 17:24:20 +01:00
Sebastian Markgraf
a754002f4e Revert approach to fixed nutritional values. 2020-11-06 17:22:21 +01:00
Sebastian Markgraf
31dabd4757 Add nutritions output 2020-10-18 17:31:56 +02:00
Sebastian Markgraf
7a89015ac5 Add migrations. 2020-10-16 23:59:19 +02:00
Sebastian Markgraf
2b1cde2efc Add basic output for nutritions. 2020-10-16 23:44:18 +02:00
Sebastian Markgraf
368d631602 Add nutrition to model. 2020-10-15 22:03:25 +02:00
48 changed files with 4955 additions and 1992 deletions

View File

@@ -8,14 +8,25 @@ ALLOWED_HOSTS=*
# random secret key, use for example base64 /dev/urandom | head -c50 to generate one
SECRET_KEY=
# your default timezone
TIMEZONE=Europe/Berlin
# add only a database password if you want to run with the default postgres, otherwise change settings accordingly
DB_ENGINE=django.db.backends.postgresql_psycopg2
DB_ENGINE=django.db.backends.postgresql
POSTGRES_HOST=db_recipes
POSTGRES_PORT=5432
POSTGRES_USER=djangodb
POSTGRES_PASSWORD=
POSTGRES_DB=djangodb
# the default value for the user preference 'fractions' (enable/disable fraction support)
# when unset: 0 (disabled)
FRACTION_PREF_DEFAULT=0
# the default value for the user preference 'comments' (enable/disable commenting system)
# when unset: 1 (true)
COMMENT_PREF_DEFAULT=1
# Users can set a amount of time after which the shopping list is refreshed when they are in viewing mode
# This is the minimum interval users can set. Setting this to low will allow users to refresh very frequently which
# might cause high load on the server. (Technically they can obviously refresh as often as they want with their own scripts)
@@ -33,14 +44,9 @@ SHOPPING_MIN_AUTOSYNC_INTERVAL=5
# when unset: 1 (true) - this is temporary until an appropriate amount of time has passed for everyone to migrate
GUNICORN_MEDIA=0
# allow authentication via reverse proxy (e.g. authelia), leave of if you dont know what you are doing
# docs: https://github.com/vabene1111/recipes/tree/develop/docs/docker/nginx-proxy%20with%20proxy%20authentication
# when unset: 0 (false)
REVERSE_PROXY_AUTH=0
# the default value for the user preference 'comments' (enable/disable commenting system)
# when unset: 1 (true)
COMMENT_PREF_DEFAULT=1

View File

@@ -172,3 +172,10 @@ class ShareLinkAdmin(admin.ModelAdmin):
admin.site.register(ShareLink, ShareLinkAdmin)
class NutritionInformationAdmin(admin.ModelAdmin):
list_display = ('id',)
admin.site.register(NutritionInformation, NutritionInformationAdmin)

View File

@@ -31,11 +31,12 @@ class UserPreferenceForm(forms.ModelForm):
class Meta:
model = UserPreference
fields = ('default_unit', 'theme', 'nav_color', 'default_page', 'show_recent', 'search_style', 'plan_share', 'ingredient_decimals', 'shopping_auto_sync', 'comments')
fields = ('default_unit', 'use_fractions', 'theme', 'nav_color', 'default_page', 'show_recent', 'search_style', 'plan_share', 'ingredient_decimals', 'shopping_auto_sync', 'comments')
help_texts = {
'nav_color': _('Color of the top navigation bar. Not all colors work with all themes, just try them out!'),
'default_unit': _('Default Unit to be used when inserting a new ingredient into a recipe.'),
'use_fractions': _('Enables support for fractions in ingredient amounts (e.g. convert decimals to fractions automatically)'),
'plan_share': _('Default user to share newly created meal plan entries with.'),
'show_recent': _('Show recently viewed recipes on search page.'),
'ingredient_decimals': _('Number of decimals to round ingredients.'),

View File

@@ -1,6 +1,7 @@
import json
import random
import re
import unicodedata
from json import JSONDecodeError
import microdata
@@ -76,11 +77,19 @@ def find_recipe_json(ld_json, url):
if len(ingredient_split) > 2:
ingredient = " ".join(ingredient_split[2:])
unit = ingredient_split[1]
try:
amount = float(ingredient_split[0].replace(',', '.'))
except ValueError:
amount = 0
ingredient = " ".join(ingredient_split)
if 'fraction' in unicodedata.decomposition(ingredient_split[0]):
frac_split = unicodedata.decomposition(ingredient_split[0]).split()
amount = round(float((frac_split[1]).replace('003', '')) / float((frac_split[3]).replace('003', '')), 3)
else:
raise TypeError
except TypeError: # raised by unicodedata.decomposition if there was no unicode character in parsed data
try:
amount = float(ingredient_split[0].replace(',', '.'))
except ValueError:
amount = 0
ingredient = " ".join(ingredient_split)
if len(ingredient_split) == 2:
ingredient = " ".join(ingredient_split[1:])
unit = ''

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,36 @@
# Generated by Django 3.1.1 on 2020-11-17 21:22
import datetime
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0088_shoppinglist_finished'),
]
operations = [
migrations.CreateModel(
name='NutritionInformation',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('fats', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('carbohydrates', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('proteins', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('calories', models.DecimalField(decimal_places=16, default=0, max_digits=32)),
('source', models.CharField(blank=True, default='', max_length=512, null=True)),
],
),
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=datetime.date(2020, 12, 1)),
),
migrations.AddField(
model_name='recipe',
name='nutrition',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='cookbook.nutritioninformation'),
),
]

View File

@@ -0,0 +1,24 @@
# Generated by Django 3.1.3 on 2020-12-14 12:59
import datetime
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('cookbook', '0089_auto_20201117_2222'),
]
operations = [
migrations.AddField(
model_name='userpreference',
name='use_fractions',
field=models.BooleanField(default=False),
),
migrations.AlterField(
model_name='invitelink',
name='valid_until',
field=models.DateField(default=datetime.date(2020, 12, 28)),
),
]

View File

@@ -9,7 +9,7 @@ from django.utils.translation import gettext as _
from django.db import models
from django_random_queryset import RandomManager
from recipes.settings import COMMENT_PREF_DEFAULT
from recipes.settings import COMMENT_PREF_DEFAULT, FRACTION_PREF_DEFAULT
def get_user_name(self):
@@ -69,6 +69,7 @@ class UserPreference(models.Model):
theme = models.CharField(choices=THEMES, max_length=128, default=FLATLY)
nav_color = models.CharField(choices=COLORS, max_length=128, default=PRIMARY)
default_unit = models.CharField(max_length=32, default='g')
use_fractions = models.BooleanField(default=FRACTION_PREF_DEFAULT)
default_page = models.CharField(choices=PAGES, max_length=64, default=SEARCH)
search_style = models.CharField(choices=SEARCH_STYLE, max_length=64, default=LARGE)
show_recent = models.BooleanField(default=True)
@@ -182,6 +183,17 @@ class Step(models.Model):
ordering = ['order', 'pk']
class NutritionInformation(models.Model):
fats = models.DecimalField(default=0, decimal_places=16, max_digits=32)
carbohydrates = models.DecimalField(default=0, decimal_places=16, max_digits=32)
proteins = models.DecimalField(default=0, decimal_places=16, max_digits=32)
calories = models.DecimalField(default=0, decimal_places=16, max_digits=32)
source = models.CharField(max_length=512, default="", null=True, blank=True)
def __str__(self):
return f'Nutrition'
class Recipe(models.Model):
name = models.CharField(max_length=128)
image = models.ImageField(upload_to='recipes/', blank=True, null=True)
@@ -195,6 +207,7 @@ class Recipe(models.Model):
working_time = models.IntegerField(default=0)
waiting_time = models.IntegerField(default=0)
internal = models.BooleanField(default=False)
nutrition = models.ForeignKey(NutritionInformation, blank=True, null=True, on_delete=models.CASCADE)
created_by = models.ForeignKey(User, on_delete=models.PROTECT)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)

View File

@@ -6,7 +6,7 @@ from rest_framework import serializers
from rest_framework.exceptions import ValidationError
from cookbook.models import MealPlan, MealType, Recipe, ViewLog, UserPreference, Storage, Sync, SyncLog, Keyword, Unit, Ingredient, Comment, RecipeImport, RecipeBook, RecipeBookEntry, ShareLink, CookLog, Food, Step, ShoppingList, \
ShoppingListEntry, ShoppingListRecipe
ShoppingListEntry, ShoppingListRecipe, NutritionInformation
from cookbook.templatetags.custom_tags import markdown
@@ -140,13 +140,20 @@ class StepSerializer(WritableNestedModelSerializer):
fields = ('id', 'name', 'type', 'instruction', 'ingredients', 'time', 'order', 'show_as_header')
class NutritionInformationSerializer(serializers.ModelSerializer):
class Meta:
model = NutritionInformation
fields = ('carbohydrates', 'fats', 'proteins', 'calories', 'source')
class RecipeSerializer(WritableNestedModelSerializer):
nutrition = NutritionInformationSerializer(allow_null=True, required=False)
steps = StepSerializer(many=True)
keywords = KeywordSerializer(many=True)
class Meta:
model = Recipe
fields = ['id', 'name', 'image', 'keywords', 'steps', 'working_time', 'waiting_time', 'created_by', 'created_at', 'updated_at', 'internal']
fields = ['id', 'name', 'image', 'keywords', 'steps', 'working_time', 'waiting_time', 'created_by', 'created_at', 'updated_at', 'internal', 'nutrition']
read_only_fields = ['image', 'created_by', 'created_at']

View File

@@ -0,0 +1,43 @@
/* frac.js (C) 2012-present SheetJS -- http://sheetjs.com */
/*https://developer.aliyun.com/mirror/npm/package/frac/v/0.3.0 Apache license*/
var frac = function frac(x, D, mixed) {
var n1 = Math.floor(x), d1 = 1;
var n2 = n1+1, d2 = 1;
if(x !== n1) while(d1 <= D && d2 <= D) {
var m = (n1 + n2) / (d1 + d2);
if(x === m) {
if(d1 + d2 <= D) { d1+=d2; n1+=n2; d2=D+1; }
else if(d1 > d2) d2=D+1;
else d1=D+1;
break;
}
else if(x < m) { n2 = n1+n2; d2 = d1+d2; }
else { n1 = n1+n2; d1 = d1+d2; }
}
if(d1 > D) { d1 = d2; n1 = n2; }
if(!mixed) return [0, n1, d1];
var q = Math.floor(n1/d1);
return [q, n1 - q*d1, d1];
};
frac.cont = function cont(x, D, mixed) {
var sgn = x < 0 ? -1 : 1;
var B = x * sgn;
var P_2 = 0, P_1 = 1, P = 0;
var Q_2 = 1, Q_1 = 0, Q = 0;
var A = Math.floor(B);
while(Q_1 < D) {
A = Math.floor(B);
P = A * P_1 + P_2;
Q = A * Q_1 + Q_2;
if((B - A) < 0.00000005) break;
B = 1 / (B - A);
P_2 = P_1; P_1 = P;
Q_2 = Q_1; Q_1 = Q;
}
if(Q > D) { if(Q_1 > D) { Q = Q_2; P = P_2; } else { Q = Q_1; P = P_1; } }
if(!mixed) return [0, sgn * P, Q];
var q = Math.floor(sgn * P/Q);
return [q, sgn*P - q*Q, Q];
};
// eslint-disable-next-line no-undef
if(typeof module !== 'undefined' && typeof DO_NOT_EXPORT_FRAC === 'undefined') module.exports = frac;

View File

@@ -0,0 +1,146 @@
/**
* Vue Cookies v1.7.4
* https://github.com/cmp-cc/vue-cookies
*
* Copyright 2016, cmp-cc
* Released under the MIT license
*/
(function () {
var defaultConfig = {
expires: '1d',
path: '; path=/',
domain: '',
secure: '',
sameSite: '; SameSite=Lax'
};
var VueCookies = {
// install of Vue
install: function (Vue) {
Vue.prototype.$cookies = this;
Vue.$cookies = this;
},
config: function (expireTimes, path, domain, secure, sameSite) {
defaultConfig.expires = expireTimes ? expireTimes : '1d';
defaultConfig.path = path ? '; path=' + path : '; path=/';
defaultConfig.domain = domain ? '; domain=' + domain : '';
defaultConfig.secure = secure ? '; Secure' : '';
defaultConfig.sameSite = sameSite ? '; SameSite=' + sameSite : '; SameSite=Lax';
},
get: function (key) {
var value = decodeURIComponent(document.cookie.replace(new RegExp('(?:(?:^|.*;)\\s*' + encodeURIComponent(key).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=\\s*([^;]*).*$)|^.*$'), '$1')) || null;
if (value && value.substring(0, 1) === '{' && value.substring(value.length - 1, value.length) === '}') {
try {
value = JSON.parse(value);
} catch (e) {
return value;
}
}
return value;
},
set: function (key, value, expireTimes, path, domain, secure, sameSite) {
if (!key) {
throw new Error('Cookie name is not find in first argument.');
} else if (/^(?:expires|max\-age|path|domain|secure|SameSite)$/i.test(key)) {
throw new Error('Cookie key name illegality, Cannot be set to ["expires","max-age","path","domain","secure","SameSite"]\t current key name: ' + key);
}
// support json object
if (value && value.constructor === Object) {
value = JSON.stringify(value);
}
var _expires = '';
expireTimes = expireTimes == undefined ? defaultConfig.expires : expireTimes;
if (expireTimes && expireTimes != 0) {
switch (expireTimes.constructor) {
case Number:
if (expireTimes === Infinity || expireTimes === -1) _expires = '; expires=Fri, 31 Dec 9999 23:59:59 GMT';
else _expires = '; max-age=' + expireTimes;
break;
case String:
if (/^(?:\d+(y|m|d|h|min|s))$/i.test(expireTimes)) {
// get capture number group
var _expireTime = expireTimes.replace(/^(\d+)(?:y|m|d|h|min|s)$/i, '$1');
// get capture type group , to lower case
switch (expireTimes.replace(/^(?:\d+)(y|m|d|h|min|s)$/i, '$1').toLowerCase()) {
// Frequency sorting
case 'm':
_expires = '; max-age=' + +_expireTime * 2592000;
break; // 60 * 60 * 24 * 30
case 'd':
_expires = '; max-age=' + +_expireTime * 86400;
break; // 60 * 60 * 24
case 'h':
_expires = '; max-age=' + +_expireTime * 3600;
break; // 60 * 60
case 'min':
_expires = '; max-age=' + +_expireTime * 60;
break; // 60
case 's':
_expires = '; max-age=' + _expireTime;
break;
case 'y':
_expires = '; max-age=' + +_expireTime * 31104000;
break; // 60 * 60 * 24 * 30 * 12
default:
new Error('unknown exception of "set operation"');
}
} else {
_expires = '; expires=' + expireTimes;
}
break;
case Date:
_expires = '; expires=' + expireTimes.toUTCString();
break;
}
}
document.cookie =
encodeURIComponent(key) + '=' + encodeURIComponent(value) +
_expires +
(domain ? '; domain=' + domain : defaultConfig.domain) +
(path ? '; path=' + path : defaultConfig.path) +
(secure == undefined ? defaultConfig.secure : secure ? '; Secure' : '') +
(sameSite == undefined ? defaultConfig.sameSite : (sameSite ? '; SameSite=' + sameSite : ''));
return this;
},
remove: function (key, path, domain) {
if (!key || !this.isKey(key)) {
return false;
}
document.cookie = encodeURIComponent(key) +
'=; expires=Thu, 01 Jan 1970 00:00:00 GMT' +
(domain ? '; domain=' + domain : defaultConfig.domain) +
(path ? '; path=' + path : defaultConfig.path) +
'; SameSite=Lax';
return this;
},
isKey: function (key) {
return (new RegExp('(?:^|;\\s*)' + encodeURIComponent(key).replace(/[\-\.\+\*]/g, '\\$&') + '\\s*\\=')).test(document.cookie);
},
keys: function () {
if (!document.cookie) return [];
var _keys = document.cookie.replace(/((?:^|\s*;)[^\=]+)(?=;|$)|^\s*|\s*(?:\=[^;]*)?(?:\1|$)/g, '').split(/\s*(?:\=[^;]*)?;\s*/);
for (var _index = 0; _index < _keys.length; _index++) {
_keys[_index] = decodeURIComponent(_keys[_index]);
}
return _keys;
}
};
if (typeof exports == 'object') {
module.exports = VueCookies;
} else if (typeof define == 'function' && define.amd) {
define([], function () {
return VueCookies;
});
} else if (window.Vue) {
Vue.use(VueCookies);
}
// vue-cookies can exist independently,no dependencies library
if (typeof window !== 'undefined') {
window.$cookies = VueCookies;
}
})();

View File

@@ -80,6 +80,35 @@
</multiselect>
</div>
</div>
<template v-if="recipe !== undefined">
<div class="row" v-if="recipe.nutrition" style="margin-top: 1vh">
<div class="col-md-12">
<div class="card border-grey">
<div class="card-body">
<h4 class="card-title">{% trans 'Nutrition' %}</h4>
<div class="dropdown-menu dropdown-menu-right"
aria-labelledby="dropdownMenuLink">
<button class="dropdown-item" @click="removeStep(step)"><i
class="fa fa-trash fa-fw"></i> {% trans 'Delete Step' %}</button>
</div>
<label for="id_name"> {% trans 'Calories' %}</label>
<input class="form-control" id="id_calories" v-model="recipe.nutrition.calories">
<label for="id_name"> {% trans 'Carbohydrates' %}</label>
<input class="form-control" id="id_carbohydrates" v-model="recipe.nutrition.carbohydrates">
<label for="id_name"> {% trans 'Fats' %}</label>
<input class="form-control" id="id_fats" v-model="recipe.nutrition.fats">
<label for="id_name"> {% trans 'Proteins' %}</label>
<input class="form-control" id="id_proteins" v-model="recipe.nutrition.proteins">
<br/>
</div>
</div>
</div>
</div>
</template>
<draggable :list="recipe.steps" group="steps"
@@ -319,7 +348,7 @@
<div class="col-md-12">
<label :for="'id_instruction_' + step.id">{% trans 'Instructions' %}</label>
<b-form-textarea class="form-control" rows="2" max-rows="20" v-model="step.instruction"
:id="'id_instruction_' + step.id"></b-form-textarea>
:id="'id_instruction_' + step.id"></b-form-textarea>
<small class="text-muted">{% trans 'You can use markdown to format this field. See the <a href="/docs/markdown/">docs here</a>' %}</small>
</div>
</div>
@@ -327,7 +356,7 @@
</div>
</draggable>
<div class="row" style="margin-top: 1vh; margin-bottom: 8vh">
<div class="row" style="margin-top: 1vh; margin-bottom: 8vh" v-if="recipe !== undefined">
<div class="col-12">
<button type="button" @click="updateRecipe(true)"
class="btn btn-success shadow-none">{% trans 'Save & View' %}</button>
@@ -335,6 +364,11 @@
class="btn btn-info shadow-none">{% trans 'Save' %}</button>
<button type="button" @click="addStep()"
class="btn btn-primary shadow-none">{% trans 'Add Step' %}</button>
<button type="button" @click="addNutrition()"
class="btn btn-primary shadow-none"
v-if="recipe.nutrition === null">{% trans 'Add Nutrition' %}</button>
<button type="button" @click="removeNutrition()" v-if="recipe.nutrition !== null"
class="btn btn-warning shadow-none">{% trans 'Remove Nutrition' %}</button>
<a href="{% url 'view_recipe' recipe.pk %}" @click="addStep()"
class="btn btn-secondary shadow-none">{% trans 'View Recipe' %}</a>
<a href="{% url 'delete_recipe' recipe.pk %}"
@@ -349,7 +383,7 @@
{% endblock %}
{% block content_xl_right %}
<div class="sticky-top" style="top: 2vh; z-index: 100;">
<div class="sticky-top" style="top: 2vh; z-index: 100;" v-if="recipe !== undefined">
<div class="row">
<div class="col-md-11">
<button type="button" @click="updateRecipe(true)"
@@ -361,6 +395,12 @@
<button type="button" @click="addStep()"
class="btn btn-primary btn-block shadow-none">{% trans 'Add Step' %}</button>
<button type="button" @click="addNutrition()"
class="btn btn-primary btn-block shadow-none"
v-if="recipe.nutrition === null">{% trans 'Add Nutrition' %}</button>
<button type="button" @click="removeNutrition()" v-if="recipe.nutrition !== null"
class="btn btn-warning btn-block shadow-none">{% trans 'Remove Nutrition' %}</button>
<a href="{% url 'view_recipe' recipe.pk %}"
class="btn btn-secondary btn-block shadow-none">{% trans 'View Recipe' %}</a>
<a href="{% url 'delete_recipe' recipe.pk %}"
@@ -455,8 +495,8 @@
e.preventDefault(); // present "Save Page" from getting triggered.
for (el of e.path) {
if(el.id !== undefined && el.id.includes('id_card_step_')) {
let step = this.recipe.steps[el.id.replace('id_card_step_','')]
if (el.id !== undefined && el.id.includes('id_card_step_')) {
let step = this.recipe.steps[el.id.replace('id_card_step_', '')]
this.addIngredient(step)
}
}
@@ -649,6 +689,12 @@
scrollToStep: function (step_index) {
document.getElementById('id_step_' + step_index).scrollIntoView({behavior: 'smooth'});
},
addNutrition: function () {
this.recipe.nutrition = {}
},
removeNutrition: function () {
this.recipe.nutrition = null
}
}
});
</script>

View File

@@ -12,6 +12,7 @@
<script src="{% static 'js/Sortable.min.js' %}"></script>
<script src="{% static 'js/vuedraggable.umd.min.js' %}"></script>
<script src="{% static 'js/vue-cookies.js' %}"></script>
<script src="{% static 'js/js.cookie.min.js' %}"></script>
@@ -24,14 +25,15 @@
<div class="col-md-4 offset-md-4">
<div class="input-group" style="margin-top: 8px; margin-bottom: 8px">
<div class="input-group-prepend">
<button class="btn btn-outline-secondary shadow-none" @click="changeWeek(-1)">
<button class="btn btn-outline-secondary shadow-none"
@click="changeStartDate(number_of_days * -1)">
<i class="fas fa-arrow-left"></i>
</button>
</div>
<input name="week" id="id_week" class="form-control" type="week" v-model="week"
<input name="date" id="id_date" class="form-control" type="date" v-model="start_date"
@change="updatePlan()">
<div class="input-group-append">
<button class="btn btn-outline-secondary shadow-none" @click="changeWeek(1)">
<button class="btn btn-outline-secondary shadow-none" @click="changeStartDate(number_of_days)">
<i class="fas fa-arrow-right"></i>
</button>
</div>
@@ -41,10 +43,10 @@
<div class="row">
<div class="col-md-12">
<table class="table table-sm table-striped table-responsive-sm">
<table class="table table-sm table-striped table-responsive-sm" style=" table-layout:fixed;">
<thead class="thead-dark">
<tr>
<th v-for="d in days" style="width: 14.2%; text-align: center">[[d]]<br/>[[formatDateDay(d)]].
<th v-for="d in dates" style="width: 14.2%; text-align: center">[[formatDateDayname(d)]]<br/>[[formatDateDay(d)]].
<button class="btn btn-sm btn-outline-secondary shadow-none" @click="addDayToShopping(d)"><i
class="fas fa-cart-plus fa-sm"></i></button>
</th>
@@ -52,7 +54,7 @@
</thead>
<tbody v-for="t in meal_types">
<tr v-if="meal_plan[t.name] !== undefined">
<td colspan="7" style="text-align: center">
<td :colspan="number_of_days" style="text-align: center">
[[ meal_plan[t.name].name]]
<template
v-if="t.created_by !== {{ request.user.pk }} && user_names[t.created_by] !== undefined">
@@ -66,18 +68,21 @@
@change="dragChanged(d.date, t, $event)"
:empty-insert-threshold="10" handle=".handle">
<div class="" v-for="(element, index) in d.items" :key="element.id">
<!-- small layout with handle -->
<div class="d-block d-md-none">
<div class="col-">
<i class="fas fa-arrows-alt handle input-group-text"
style="width: 100%"></i>
</div>
<div class="list-group-item">
<div class="list-group-item" style="word-wrap: break-word;">
<a href="#" @click="plan_detail = element" data-toggle="modal"
data-target="#id_plan_detail_modal">[[ planElementName(element)]]</a>
</div>
</div>
<div class="list-group-item handle d-md-block d-none">
<div class="col-md-12">
<!-- big layout -->
<div class="list-group-item handle d-md-block d-none"
style="word-wrap: break-word; padding: 2;margin-bottom: 4">
<div class="col-md-12" style="padding: 0">
<a href="#" @click="plan_detail = element" data-toggle="modal"
data-target="#id_plan_detail_modal">[[ planElementName(element)]]</a>
</div>
@@ -107,7 +112,8 @@
<input type="text" class="form-control" v-model="recipe_query" @keyup="getRecipes"
placeholder="{% trans 'Search Recipe' %}">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" @click="getRandomRecipes">
<button class="btn btn-outline-secondary" type="button"
@click="getRandomRecipes">
<i class="fas fa-dice"></i>
</button>
</div>
@@ -180,6 +186,29 @@
</div>
<div class="card-body">
<div class="row">
<div class="col">
<label>
{% trans 'Number of Days' %}
<input class="form-control" type="number" v-model="number_of_days"
@change="updatePlan(); $cookies.set('number_of_days',number_of_days)">
</label>
</div>
</div>
<div class="row">
<div class="col">
<label>
{% trans 'Weekday offset' %}
<input class="form-control" type="number" v-model="start_offset"
@change="updatePlan(); $cookies.set('start_offset',start_offset)">
<small class="text-muted">{% trans 'Number of days starting from the first day of the week to offset the default view.' %}</small>
</label>
</div>
</div>
<a href="#" data-toggle="modal"
data-target="#id_plan_types_modal">{% trans 'Edit plan types' %}</a> <br/>
<a href="#" data-toggle="modal"
@@ -345,8 +374,10 @@
delimiters: ['[[', ']]'],
el: '#app',
data: {
week: moment().format('YYYY-[W]WW'),
days: moment.weekdays(true),
start_date: undefined,
start_offset: 0,
dates: [],
number_of_days: $cookies.isKey('number_of_days') ? $cookies.get('number_of_days') : 7,
plan_entries: [],
meal_types: [],
meal_types_edit: [],
@@ -374,6 +405,9 @@
this.$set(this.user_names, {{ request.user.pk }}, '{{ request.user.get_user_name }}')
this.user_id_update = Array.from(this.default_shared_users)
this.start_offset = $cookies.isKey('start_offset') ? $cookies.get('start_offset') : 0;
this.start_date = moment().weekday(0).add(this.start_offset, 'days').format('YYYY-MM-DD')
this.updatePlan();
this.getRecipes();
},
@@ -388,6 +422,11 @@
})
},
updatePlan: function () {
this.dates = [];
for (var i = 0; i <= (this.number_of_days - 1); i++) {
this.dates.push(moment(this.start_date).add(i, 'days'));
}
let planEntryPromise = this.getPlanEntries();
let planTypePromise = this.getPlanTypes();
@@ -396,7 +435,7 @@
})
},
getPlanEntries: function () {
return this.$http.get("{% url 'api:mealplan-list' %}?html_week=" + this.week).then((response) => {
return this.$http.get("{% url 'api:mealplan-list' %}?from_date=" + this.dates[0].format('YYYY-MM-DD') + "&to_date=" + this.dates[this.dates.length - 1].format('YYYY-MM-DD')).then((response) => {
this.plan_entries = response.data;
}).catch((err) => {
console.log("getPlanEntries error: ", err);
@@ -431,11 +470,10 @@
meal_type: t.id,
days: {}
})
for (let d of this.days) {
let date = moment(this.week).weekday(this.days.indexOf(d)).format('YYYY-MM-DD')
this.$set(this.meal_plan[t.name].days, date, {
name: d,
date: date,
for (let d of this.dates) {
this.$set(this.meal_plan[t.name].days, d.format('YYYY-MM-DD'), {
name: this.formatDateDayname(d),
date: d.format('YYYY-MM-DD'),
items: []
})
}
@@ -632,11 +670,14 @@
formatLocalDate: function (date) {
return moment(date).format('LL')
},
formatDateDay: function (day) {
return moment(this.week).weekday(this.days.indexOf(day)).format('D')
formatDateDay: function (date) {
return moment(date).format('D')
},
changeWeek: function (change) {
this.week = moment(this.week).add(change, 'w').format('YYYY-[W]WW')
formatDateDayname: function (date) {
return moment(date).format('dddd')
},
changeStartDate: function (change) {
this.start_date = moment(this.start_date).add(change, 'days').format('YYYY-MM-DD')
this.updatePlan();
},
getShoppingUrl: function () {
@@ -653,13 +694,14 @@
return url
},
getIcalUrl: function () {
return "{% url 'api_get_plan_ical' 12345 %}".replace(/12345/, this.week);
if (this.dates.length === 0) {
return ""
}
return "{% url 'api_get_plan_ical' 12345 6789 %}".replace(/12345/, this.dates[0].format('YYYY-MM-DD')).replace(/6789/, this.dates[this.dates.length - 1].format('YYYY-MM-DD'));
},
addDayToShopping: function (day) {
let date = moment(this.week).weekday(this.days.indexOf(day)).format('YYYY-MM-DD')
addDayToShopping: function (date) {
for (let t of this.meal_types) {
for (let i of this.meal_plan[t.name].days[date].items) {
for (let i of this.meal_plan[t.name].days[date.format('YYYY-MM-DD')].items) {
if (!this.shopping_list.includes(i)) {
this.shopping_list.push(i)
}

View File

@@ -12,6 +12,8 @@
{% include 'include/vue_base.html' %}
<script src="{% static 'js/moment-with-locales.min.js' %}"></script>
<script src="{% static 'js/frac.js' %}"></script>
<link rel="stylesheet" href="{% static 'css/pretty-checkbox.min.css' %}">
<link rel="stylesheet" href="{% static 'custom/css/markdown_blockquote.css' %}">
@@ -77,13 +79,13 @@
{% if recipe.working_time and recipe.working_time != 0 %}
<span class="badge badge-secondary"><i
class="fas fa-user-clock"></i> {% trans 'Preparation time ca.' %} {{ recipe.working_time }} min </span>
class="fas fa-user-clock"></i> {% trans 'Preparation time ~' %} {{ recipe.working_time }} min </span>
{% endif %}
{% if recipe.waiting_time and recipe.waiting_time != 0 %}
<span
class="badge badge-secondary"><i
class="far fa-clock"></i> {% trans 'Waiting time ca.' %} {{ recipe.waiting_time }} min </span>
class="far fa-clock"></i> {% trans 'Waiting time ~' %} {{ recipe.waiting_time }} min </span>
{% endif %}
{% recipe_last recipe request.user as last_cooked %}
{% if last_cooked %}
@@ -150,7 +152,7 @@
<span>&#x2063;</span>
</template>
<template v-if="!i.no_amount">
<span>[[roundDecimals(i.amount * ingredient_factor)]]</span>
<span v-html="calculateAmount(i.amount)"></span>
{# Allow for amounts without units, such as "2 eggs" #}
<template v-if="i.unit">
[[i.unit.name]]
@@ -208,6 +210,60 @@
{% endif %}
</div>
{% if recipe.nutrition %}
<div class="row mt-5">
<div class="col-md-6 order-md-1 col-sm-12 order-sm-2 col-12 order-2">
<div class="card border-primary">
<div class="card-body">
<h4 class="card-title">{% trans 'Nutrition' %}</h4>
<table class="table table-sm">
<tr>
<td style="padding-top: 8px!important; ">
<b>{% trans 'Calories' %}</b>
</td>
<td style="text-align: right">{{ recipe.nutrition.calories|floatformat:2 }}</td>
<td>kcal</td>
</tr>
<tr>
<tr>
<td style="padding-top: 8px!important; ">
<b>{% trans 'Carbohydrates' %}</b>
</td>
<td style="text-align: right">{{ recipe.nutrition.carbohydrates|floatformat:2 }}</td>
<td>g</td>
</tr>
<tr>
<tr>
<td style="padding-top: 8px!important; ">
<b>{% trans 'Fats' %}</b>
</td>
<td style="text-align: right">{{ recipe.nutrition.fats|floatformat:2 }}</td>
<td>g</td>
</tr>
<tr>
<tr>
<td style="padding-top: 8px!important; ">
<b>{% trans 'Proteins' %}</b>
</td>
<td style="text-align: right">{{ recipe.nutrition.proteins|floatformat:2 }}</td>
<td>g</td>
</tr>
<tr>
<td></td>
<td></td>
<td></td>
</tr>
</table>
{% if recipe.nutrition.source %}
Source: {{ recipe.nutrition.source }}
{% endif %}
</div>
</div>
</div>
</div>
{% endif %}
<div v-if="recipe !== undefined && recipe.steps.length > 0">
<hr>
<h3>{% trans 'Instructions' %}</h3>
@@ -271,7 +327,7 @@
<span>&#x2063;</span>
</template>
<template v-if="!i.no_amount">
<span>[[roundDecimals(i.amount * ingredient_factor)]]</span>
<span v-html="calculateAmount(i.amount)"></span>
{# Allow for amounts without units, such as "2 eggs" #}
<template v-if="i.unit">
[[i.unit.name]]
@@ -478,7 +534,7 @@
this.$http.get("{% url 'api:recipe-detail' recipe.pk %}" {% if share %}
+ "?share={{ share }}"{% endif %}).then((response) => {
this.recipe = response.data;
this.loading = false
this.loading = false;
for (let step of this.recipe.steps) {
if (step.ingredients.length > 0) {
@@ -487,25 +543,25 @@
if (step.time !== 0) {
this.has_times = true
}
this.$set(step, 'time_finished', undefined)
this.$set(step, 'time_finished', undefined);
for (let i of step.ingredients) {
this.$set(i, 'checked', false)
}
}
}).catch((err) => {
this.error = err.data
this.loading = false
this.error = err.data;
this.loading = false;
console.log(err)
})
},
roundDecimals: function (num) {
let decimals = {% if request.user.userpreference.ingredient_decimals %}
{{ request.user.userpreference.ingredient_decimals }} {% else %} 2 {% endif %}
{{ request.user.userpreference.ingredient_decimals }} {% else %} 2; {% endif %}
return +(Math.round(num + `e+${decimals}`) + `e-${decimals}`);
},
updateTimes: function (step) {
let time_diff_first = 0
let time_diff_first = 0;
for (let s of this.recipe.steps) {
if (this.recipe.steps.indexOf(s) < this.recipe.steps.indexOf(step)) {
time_diff_first += s.time
@@ -514,7 +570,7 @@
this.recipe.steps[0].time_finished = moment(step.time_finished).subtract(time_diff_first, 'minutes').format(moment.HTML5_FMT.DATETIME_LOCAL);
let time_diff = 0
let time_diff = 0;
for (let s of this.recipe.steps) {
s.time_finished = moment(this.recipe.steps[0].time_finished).add(time_diff, 'minutes').format(moment.HTML5_FMT.DATETIME_LOCAL);
time_diff += s.time
@@ -523,8 +579,28 @@
},
getShoppingUrl: function () {
return `{% url 'view_shopping' %}?r=[${this.recipe.id},${this.ingredient_factor}]`
}
},
calculateAmount: function (amount) {
{% if request.user.userpreference.use_fractions %}
let return_string = ''
let fraction = frac.cont((amount * this.ingredient_factor), 9, true)
if (fraction[0] > 0) {
return_string += fraction[0]
}
if (fraction[1] > 0) {
return_string += ` <sup>${(fraction[1])}</sup>&frasl;<sub>${(fraction[2])}</sub>`
}
return return_string
{% else %}
return this.roundDecimals(amount * this.ingredient_factor)
{% endif %}
},
}
});
</script>
{% endblock %}

View File

@@ -73,7 +73,7 @@ urlpatterns = [
path('api/get_recipe_file/<int:recipe_id>/', api.get_recipe_file, name='api_get_recipe_file'),
path('api/sync_all/', api.sync_all, name='api_sync'),
path('api/log_cooking/<int:recipe_id>/', api.log_cooking, name='api_log_cooking'),
path('api/plan-ical/<slug:html_week>/', api.get_plan_ical, name='api_get_plan_ical'),
path('api/plan-ical/<slug:from_date>/<slug:to_date>/', api.get_plan_ical, name='api_get_plan_ical'),
path('api/recipe-from-url/', api.recipe_from_url, name='api_recipe_from_url'),
path('api/backup/', api.get_backup, name='api_backup'),

View File

@@ -104,8 +104,12 @@ class StandardFilterMixin(ViewSetMixin):
queryset = queryset.filter(name__icontains=query)
limit = self.request.query_params.get('limit', None)
random = self.request.query_params.get('random', False)
if limit is not None:
queryset = queryset[:int(limit)]
if random:
queryset = queryset.random(int(limit))
else:
queryset = queryset[:int(limit)]
return queryset
@@ -150,7 +154,8 @@ class MealPlanViewSet(viewsets.ModelViewSet):
list:
optional parameters
- **html_week**: filter for a calendar week (format 2020-W24 as html input type week)
- **from_date**: filter from (inclusive) a certain date onward
- **to_date**: filter upward to (inclusive) certain date
"""
queryset = MealPlan.objects.all()
@@ -159,10 +164,14 @@ class MealPlanViewSet(viewsets.ModelViewSet):
def get_queryset(self):
queryset = MealPlan.objects.filter(Q(created_by=self.request.user) | Q(shared=self.request.user)).distinct().all()
week = self.request.query_params.get('html_week', None)
if week is not None:
y, w = week.replace('-W', ' ').split()
queryset = queryset.filter(date__week=w, date__year=y)
from_date = self.request.query_params.get('from_date', None)
if from_date is not None:
queryset = queryset.filter(date__gte=from_date)
to_date = self.request.query_params.get('to_date', None)
if to_date is not None:
queryset = queryset.filter(date__lte=to_date)
return queryset
@@ -209,9 +218,6 @@ class RecipeViewSet(viewsets.ModelViewSet, StandardFilterMixin):
internal = self.request.query_params.get('internal', None)
if internal:
self.queryset = self.queryset.filter(internal=True)
random = self.request.query_params.get('random', False)
if random:
self.queryset = self.queryset.random(5)
return super().get_queryset()
@@ -367,11 +373,14 @@ def log_cooking(request, recipe_id):
@group_required('user')
def get_plan_ical(request, html_week):
def get_plan_ical(request, from_date, to_date):
queryset = MealPlan.objects.filter(Q(created_by=request.user) | Q(shared=request.user)).distinct().all()
y, w = html_week.replace('-W', ' ').split()
queryset = queryset.filter(date__week=w, date__year=y)
if from_date is not None:
queryset = queryset.filter(date__gte=from_date)
if to_date is not None:
queryset = queryset.filter(date__lte=to_date)
cal = Calendar()
@@ -385,7 +394,7 @@ def get_plan_ical(request, html_week):
cal.add_component(event)
response = FileResponse(io.BytesIO(cal.to_ical()))
response["Content-Disposition"] = f'attachment; filename=meal_plan_{html_week}.ics'
response["Content-Disposition"] = f'attachment; filename=meal_plan_{from_date}-{to_date}.ics'
return response

View File

@@ -202,6 +202,7 @@ def user_settings(request):
up.plan_share.set(form.cleaned_data['plan_share'])
up.ingredient_decimals = form.cleaned_data['ingredient_decimals']
up.comments = form.cleaned_data['comments']
up.use_fractions = form.cleaned_data['use_fractions']
up.shopping_auto_sync = form.cleaned_data['shopping_auto_sync']
if up.shopping_auto_sync < settings.SHOPPING_MIN_AUTOSYNC_INTERVAL:
@@ -242,7 +243,7 @@ def history(request):
@group_required('admin')
def system(request):
postgres = False if settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql_psycopg2' else True
postgres = False if (settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql_psycopg2' or settings.DATABASES['default']['ENGINE'] == 'django.db.backends.postgresql') else True
secret_key = False if os.getenv('SECRET_KEY') else True

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-29 19:44+0200\n"
"POT-Creation-Date: 2020-12-15 21:39+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,18 +18,18 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: .\recipes\settings.py:174
#: .\recipes\settings.py:175
msgid "English"
msgstr "Englisch"
#: .\recipes\settings.py:175
#: .\recipes\settings.py:176
msgid "German"
msgstr "Deutsch"
#: .\recipes\settings.py:176
#: .\recipes\settings.py:177
msgid "Dutch"
msgstr ""
#: .\recipes\settings.py:177
#: .\recipes\settings.py:178
msgid "French"
msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-29 19:44+0200\n"
"POT-Creation-Date: 2020-12-15 21:39+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,18 +18,18 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: .\recipes\settings.py:174
#: .\recipes\settings.py:175
msgid "English"
msgstr ""
#: .\recipes\settings.py:175
#: .\recipes\settings.py:176
msgid "German"
msgstr ""
#: .\recipes\settings.py:176
#: .\recipes\settings.py:177
msgid "Dutch"
msgstr ""
#: .\recipes\settings.py:177
#: .\recipes\settings.py:178
msgid "French"
msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-29 19:44+0200\n"
"POT-Creation-Date: 2020-12-15 21:39+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,18 +18,18 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: .\recipes\settings.py:174
#: .\recipes\settings.py:175
msgid "English"
msgstr ""
#: .\recipes\settings.py:175
#: .\recipes\settings.py:176
msgid "German"
msgstr ""
#: .\recipes\settings.py:176
#: .\recipes\settings.py:177
msgid "Dutch"
msgstr ""
#: .\recipes\settings.py:177
#: .\recipes\settings.py:178
msgid "French"
msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-29 19:44+0200\n"
"POT-Creation-Date: 2020-12-15 21:39+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,18 +18,18 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: .\recipes\settings.py:174
#: .\recipes\settings.py:175
msgid "English"
msgstr ""
#: .\recipes\settings.py:175
#: .\recipes\settings.py:176
msgid "German"
msgstr ""
#: .\recipes\settings.py:176
#: .\recipes\settings.py:177
msgid "Dutch"
msgstr ""
#: .\recipes\settings.py:177
#: .\recipes\settings.py:178
msgid "French"
msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-29 19:44+0200\n"
"POT-Creation-Date: 2020-12-15 21:39+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,18 +18,18 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: .\recipes\settings.py:174
#: .\recipes\settings.py:175
msgid "English"
msgstr ""
#: .\recipes\settings.py:175
#: .\recipes\settings.py:176
msgid "German"
msgstr ""
#: .\recipes\settings.py:176
#: .\recipes\settings.py:177
msgid "Dutch"
msgstr ""
#: .\recipes\settings.py:177
#: .\recipes\settings.py:178
msgid "French"
msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-29 19:44+0200\n"
"POT-Creation-Date: 2020-12-15 21:39+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -17,18 +17,18 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: .\recipes\settings.py:174
#: .\recipes\settings.py:175
msgid "English"
msgstr ""
#: .\recipes\settings.py:175
#: .\recipes\settings.py:176
msgid "German"
msgstr ""
#: .\recipes\settings.py:176
#: .\recipes\settings.py:177
msgid "Dutch"
msgstr ""
#: .\recipes\settings.py:177
#: .\recipes\settings.py:178
msgid "French"
msgstr ""

View File

@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-09-29 19:44+0200\n"
"POT-Creation-Date: 2020-12-15 21:39+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,18 +18,18 @@ msgstr ""
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
#: .\recipes\settings.py:174
#: .\recipes\settings.py:175
msgid "English"
msgstr ""
#: .\recipes\settings.py:175
#: .\recipes\settings.py:176
msgid "German"
msgstr ""
#: .\recipes\settings.py:176
#: .\recipes\settings.py:177
msgid "Dutch"
msgstr ""
#: .\recipes\settings.py:177
#: .\recipes\settings.py:178
msgid "French"
msgstr ""

View File

@@ -31,6 +31,7 @@ REVERSE_PROXY_AUTH = bool(int(os.getenv('REVERSE_PROXY_AUTH', False)))
# default value for user preference 'comment'
COMMENT_PREF_DEFAULT = bool(int(os.getenv('COMMENT_PREF_DEFAULT', True)))
FRACTION_PREF_DEFAULT = bool(int(os.getenv('FRACTION_PREF_DEFAULT', False)))
# minimum interval that users can set for automatic sync of shopping lists
SHOPPING_MIN_AUTOSYNC_INTERVAL = int(os.getenv('SHOPPING_MIN_AUTOSYNC_INTERVAL', 5))
@@ -162,7 +163,7 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = 'en'
TIME_ZONE = 'Europe/Berlin'
TIME_ZONE = os.getenv('TIMEZONE') if os.getenv('TIMEZONE') else 'Europe/Berlin'
USE_I18N = True
@@ -175,6 +176,7 @@ LANGUAGES = [
('de', _('German')),
('nl', _('Dutch')),
('fr', _('French')),
('ca', _('Catalan')),
]
# Static files (CSS, JavaScript, Images)

View File

@@ -2,7 +2,7 @@ bleach==3.2.1
bleach-whitelist==0.0.11
Django==3.1.3
django-annoying==0.10.6
django-autocomplete-light==3.5.1
django-autocomplete-light==3.8.1
django-cleanup==4.0.0
django-crispy-forms==1.9.1
django-emoji-picker==0.0.6
@@ -11,19 +11,19 @@ django-tables2==2.3.3
djangorestframework==3.11.0
drf-writable-nested==0.6.2
gunicorn==20.0.4
lxml==4.5.1
lxml==4.6.2
Markdown==3.3.3
Pillow==7.1.2
Pillow==8.0.1
psycopg2-binary==2.8.6
python-dotenv==0.15.0
requests==2.25.0
simplejson==3.17.2
six==1.15.0
webdavclient3==3.14.4
webdavclient3==3.14.5
whitenoise==5.2.0
icalendar==4.0.6
pyyaml==5.3.1
uritemplate==3.0.1
beautifulsoup4==4.9.2
beautifulsoup4==4.9.3
microdata==0.7.1
django-random-queryset==0.1.3