added support for fractions

This commit is contained in:
vabene1111 2020-12-14 14:08:11 +01:00
parent 9863447bac
commit 7732aa7646
8 changed files with 118 additions and 22 deletions

View File

@ -19,6 +19,14 @@ 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)
@ -36,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

@ -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

@ -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)

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

@ -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' %}">
@ -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]]
@ -221,7 +223,7 @@
</td>
<td style="text-align: right">{{ recipe.nutrition.calories|floatformat:2 }}</td>
<td>kcal</td>
</tr>
</tr>
<tr>
<tr>
<td style="padding-top: 8px!important; ">
@ -229,7 +231,7 @@
</td>
<td style="text-align: right">{{ recipe.nutrition.carbohydrates|floatformat:2 }}</td>
<td>g</td>
</tr>
</tr>
<tr>
<tr>
<td style="padding-top: 8px!important; ">
@ -237,23 +239,23 @@
</td>
<td style="text-align: right">{{ recipe.nutrition.fats|floatformat:2 }}</td>
<td>g</td>
</tr>
</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>
<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 }}
Source: {{ recipe.nutrition.source }}
{% endif %}
</div>
</div>
@ -325,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]]
@ -577,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, 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

@ -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:

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))