Merge branch 'develop'
# Conflicts: # docs/faq.md
This commit is contained in:
commit
abf8f79136
@ -28,7 +28,7 @@ SECRET_KEY_FILE=
|
|||||||
# ---------------------------------------------------------------
|
# ---------------------------------------------------------------
|
||||||
|
|
||||||
# your default timezone See https://timezonedb.com/time-zones for a list of timezones
|
# your default timezone See https://timezonedb.com/time-zones for a list of timezones
|
||||||
TIMEZONE=Europe/Berlin
|
TZ=Europe/Berlin
|
||||||
|
|
||||||
# add only a database password if you want to run with the default postgres, otherwise change settings accordingly
|
# add only a database password if you want to run with the default postgres, otherwise change settings accordingly
|
||||||
DB_ENGINE=django.db.backends.postgresql
|
DB_ENGINE=django.db.backends.postgresql
|
||||||
@ -183,3 +183,5 @@ REMOTE_USER_AUTH=0
|
|||||||
# Recipe exports are cached for a certain time by default, adjust time if needed
|
# Recipe exports are cached for a certain time by default, adjust time if needed
|
||||||
# EXPORT_FILE_CACHE_DURATION=600
|
# EXPORT_FILE_CACHE_DURATION=600
|
||||||
|
|
||||||
|
# if you want to do many requests to the FDC API you need to get a (free) API key. Demo key is limited to 30 requests / hour or 50 requests / day
|
||||||
|
#FDC_API_KEY=DEMO_KEY
|
@ -349,7 +349,9 @@ admin.site.register(ShareLink, ShareLinkAdmin)
|
|||||||
|
|
||||||
|
|
||||||
class PropertyTypeAdmin(admin.ModelAdmin):
|
class PropertyTypeAdmin(admin.ModelAdmin):
|
||||||
list_display = ('id', 'name')
|
search_fields = ('space',)
|
||||||
|
|
||||||
|
list_display = ('id', 'space', 'name', 'fdc_id')
|
||||||
|
|
||||||
|
|
||||||
admin.site.register(PropertyType, PropertyTypeAdmin)
|
admin.site.register(PropertyType, PropertyTypeAdmin)
|
||||||
|
19
cookbook/helper/fdc_helper.py
Normal file
19
cookbook/helper/fdc_helper.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
def get_all_nutrient_types():
|
||||||
|
f = open('') # <--- download the foundation food or any other dataset and retrieve all nutrition ID's from it https://fdc.nal.usda.gov/download-datasets.html
|
||||||
|
json_data = json.loads(f.read())
|
||||||
|
|
||||||
|
nutrients = {}
|
||||||
|
for food in json_data['FoundationFoods']:
|
||||||
|
for entry in food['foodNutrients']:
|
||||||
|
nutrients[entry['nutrient']['id']] = {'name': entry['nutrient']['name'], 'unit': entry['nutrient']['unitName']}
|
||||||
|
|
||||||
|
nutrient_ids = list(nutrients.keys())
|
||||||
|
nutrient_ids.sort()
|
||||||
|
for nid in nutrient_ids:
|
||||||
|
print('{', f'value: {nid}, text: "{nutrients[nid]["name"]} [{nutrients[nid]["unit"]}] ({nid})"', '},')
|
||||||
|
|
||||||
|
|
||||||
|
get_all_nutrient_types()
|
@ -163,10 +163,9 @@ def get_from_scraper(scrape, request):
|
|||||||
if len(recipe_json['steps']) == 0:
|
if len(recipe_json['steps']) == 0:
|
||||||
recipe_json['steps'].append({'instruction': '', 'ingredients': [], })
|
recipe_json['steps'].append({'instruction': '', 'ingredients': [], })
|
||||||
|
|
||||||
|
recipe_json['description'] = recipe_json['description'][:512]
|
||||||
if len(recipe_json['description']) > 256: # split at 256 as long descriptions don't look good on recipe cards
|
if len(recipe_json['description']) > 256: # split at 256 as long descriptions don't look good on recipe cards
|
||||||
recipe_json['steps'][0]['instruction'] = f"*{recipe_json['description']}* \n\n" + recipe_json['steps'][0]['instruction']
|
recipe_json['steps'][0]['instruction'] = f"*{recipe_json['description']}* \n\n" + recipe_json['steps'][0]['instruction']
|
||||||
else:
|
|
||||||
recipe_json['description'] = recipe_json['description'][:512]
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
for x in scrape.ingredients():
|
for x in scrape.ingredients():
|
||||||
@ -259,13 +258,14 @@ def get_from_youtube_scraper(url, request):
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
# TODO add automation here
|
|
||||||
try:
|
try:
|
||||||
automation_engine = AutomationEngine(request, source=url)
|
automation_engine = AutomationEngine(request, source=url)
|
||||||
video = YouTube(url=url)
|
video = YouTube(url)
|
||||||
|
video.streams.first() # this is required to execute some kind of generator/web request that fetches the description
|
||||||
default_recipe_json['name'] = automation_engine.apply_regex_replace_automation(video.title, Automation.NAME_REPLACE)
|
default_recipe_json['name'] = automation_engine.apply_regex_replace_automation(video.title, Automation.NAME_REPLACE)
|
||||||
default_recipe_json['image'] = video.thumbnail_url
|
default_recipe_json['image'] = video.thumbnail_url
|
||||||
default_recipe_json['steps'][0]['instruction'] = automation_engine.apply_regex_replace_automation(video.description, Automation.INSTRUCTION_REPLACE)
|
if video.description:
|
||||||
|
default_recipe_json['steps'][0]['instruction'] = automation_engine.apply_regex_replace_automation(video.description, Automation.INSTRUCTION_REPLACE)
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Django 4.2.7 on 2023-11-29 19:44
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0204_propertytype_fdc_id'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='food',
|
||||||
|
name='fdc_id',
|
||||||
|
field=models.IntegerField(blank=True, default=None, null=True),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='propertytype',
|
||||||
|
name='fdc_id',
|
||||||
|
field=models.IntegerField(blank=True, default=None, null=True),
|
||||||
|
),
|
||||||
|
]
|
@ -591,7 +591,7 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
|||||||
|
|
||||||
preferred_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_unit')
|
preferred_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_unit')
|
||||||
preferred_shopping_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_shopping_unit')
|
preferred_shopping_unit = models.ForeignKey(Unit, on_delete=models.SET_NULL, null=True, blank=True, default=None, related_name='preferred_shopping_unit')
|
||||||
fdc_id = models.CharField(max_length=128, null=True, blank=True, default=None)
|
fdc_id = models.IntegerField(null=True, default=None, blank=True)
|
||||||
|
|
||||||
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
|
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
|
||||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||||
@ -767,7 +767,7 @@ class PropertyType(models.Model, PermissionModelMixin):
|
|||||||
(PRICE, _('Price')), (GOAL, _('Goal')), (OTHER, _('Other'))), null=True, blank=True)
|
(PRICE, _('Price')), (GOAL, _('Goal')), (OTHER, _('Other'))), null=True, blank=True)
|
||||||
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
|
open_data_slug = models.CharField(max_length=128, null=True, blank=True, default=None)
|
||||||
|
|
||||||
fdc_id = models.CharField(max_length=128, null=True, blank=True, default=None)
|
fdc_id = models.IntegerField(null=True, default=None, blank=True)
|
||||||
# TODO show if empty property?
|
# TODO show if empty property?
|
||||||
# TODO formatting property?
|
# TODO formatting property?
|
||||||
|
|
||||||
@ -809,7 +809,7 @@ class FoodProperty(models.Model):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
models.UniqueConstraint(fields=['food', 'property'], name='property_unique_food')
|
models.UniqueConstraint(fields=['food', 'property'], name='property_unique_food'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ from oauth2_provider.models import AccessToken
|
|||||||
from PIL import Image
|
from PIL import Image
|
||||||
from rest_framework import serializers
|
from rest_framework import serializers
|
||||||
from rest_framework.exceptions import NotFound, ValidationError
|
from rest_framework.exceptions import NotFound, ValidationError
|
||||||
|
from rest_framework.fields import IntegerField
|
||||||
|
|
||||||
from cookbook.helper.CustomStorageClass import CachedS3Boto3Storage
|
from cookbook.helper.CustomStorageClass import CachedS3Boto3Storage
|
||||||
from cookbook.helper.HelperFunctions import str2bool
|
from cookbook.helper.HelperFunctions import str2bool
|
||||||
@ -524,6 +525,7 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer, OpenDataMo
|
|||||||
|
|
||||||
class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer, UniqueFieldsMixin):
|
class PropertyTypeSerializer(OpenDataModelMixin, WritableNestedModelSerializer, UniqueFieldsMixin):
|
||||||
id = serializers.IntegerField(required=False)
|
id = serializers.IntegerField(required=False)
|
||||||
|
order = IntegerField(default=0, required=False)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
validated_data['name'] = validated_data['name'].strip()
|
validated_data['name'] = validated_data['name'].strip()
|
||||||
@ -985,6 +987,8 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
|||||||
shared = UserSerializer(many=True, required=False, allow_null=True)
|
shared = UserSerializer(many=True, required=False, allow_null=True)
|
||||||
shopping = serializers.SerializerMethodField('in_shopping')
|
shopping = serializers.SerializerMethodField('in_shopping')
|
||||||
|
|
||||||
|
to_date = serializers.DateField(required=False)
|
||||||
|
|
||||||
def get_note_markdown(self, obj):
|
def get_note_markdown(self, obj):
|
||||||
return markdown(obj.note)
|
return markdown(obj.note)
|
||||||
|
|
||||||
@ -993,6 +997,10 @@ class MealPlanSerializer(SpacedModelSerializer, WritableNestedModelSerializer):
|
|||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
validated_data['created_by'] = self.context['request'].user
|
validated_data['created_by'] = self.context['request'].user
|
||||||
|
|
||||||
|
if 'to_date' not in validated_data or validated_data['to_date'] is None:
|
||||||
|
validated_data['to_date'] = validated_data['from_date']
|
||||||
|
|
||||||
mealplan = super().create(validated_data)
|
mealplan = super().create(validated_data)
|
||||||
if self.context['request'].data.get('addshopping', False) and self.context['request'].data.get('recipe', None):
|
if self.context['request'].data.get('addshopping', False) and self.context['request'].data.get('recipe', None):
|
||||||
SLR = RecipeShoppingEditor(user=validated_data['created_by'], space=validated_data['space'])
|
SLR = RecipeShoppingEditor(user=validated_data['created_by'], space=validated_data['space'])
|
||||||
|
31
cookbook/templates/property_editor.html
Normal file
31
cookbook/templates/property_editor.html
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
{% extends "base.html" %}
|
||||||
|
{% load render_bundle from webpack_loader %}
|
||||||
|
{% load static %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% load l10n %}
|
||||||
|
|
||||||
|
{% block title %}{% trans 'Property Editor' %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content_fluid %}
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<property-editor-view></property-editor-view>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block script %}
|
||||||
|
{% if debug %}
|
||||||
|
<script src="{% url 'js_reverse' %}"></script>
|
||||||
|
{% else %}
|
||||||
|
<script src="{% static 'django_js_reverse/reverse.js' %}"></script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<script type="application/javascript">
|
||||||
|
window.RECIPE_ID = {{ recipe_id }}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% render_bundle 'property_editor_view' %}
|
||||||
|
{% endblock %}
|
@ -43,7 +43,7 @@ router.register(r'recipe', api.RecipeViewSet)
|
|||||||
router.register(r'recipe-book', api.RecipeBookViewSet)
|
router.register(r'recipe-book', api.RecipeBookViewSet)
|
||||||
router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet)
|
router.register(r'recipe-book-entry', api.RecipeBookEntryViewSet)
|
||||||
router.register(r'unit-conversion', api.UnitConversionViewSet)
|
router.register(r'unit-conversion', api.UnitConversionViewSet)
|
||||||
router.register(r'food-property-type', api.PropertyTypeViewSet)
|
router.register(r'food-property-type', api.PropertyTypeViewSet) # TODO rename + regenerate
|
||||||
router.register(r'food-property', api.PropertyViewSet)
|
router.register(r'food-property', api.PropertyViewSet)
|
||||||
router.register(r'shopping-list', api.ShoppingListViewSet)
|
router.register(r'shopping-list', api.ShoppingListViewSet)
|
||||||
router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet)
|
router.register(r'shopping-list-entry', api.ShoppingListEntryViewSet)
|
||||||
@ -91,6 +91,7 @@ urlpatterns = [
|
|||||||
path('history/', views.history, name='view_history'),
|
path('history/', views.history, name='view_history'),
|
||||||
path('supermarket/', views.supermarket, name='view_supermarket'),
|
path('supermarket/', views.supermarket, name='view_supermarket'),
|
||||||
path('ingredient-editor/', views.ingredient_editor, name='view_ingredient_editor'),
|
path('ingredient-editor/', views.ingredient_editor, name='view_ingredient_editor'),
|
||||||
|
path('property-editor/<int:pk>', views.property_editor, name='view_property_editor'),
|
||||||
path('abuse/<slug:token>', views.report_share_abuse, name='view_report_share_abuse'),
|
path('abuse/<slug:token>', views.report_share_abuse, name='view_report_share_abuse'),
|
||||||
|
|
||||||
path('api/import/', api.import_files, name='view_import'),
|
path('api/import/', api.import_files, name='view_import'),
|
||||||
|
@ -26,7 +26,7 @@ from django.db.models import Case, Count, Exists, OuterRef, ProtectedError, Q, S
|
|||||||
from django.db.models.fields.related import ForeignObjectRel
|
from django.db.models.fields.related import ForeignObjectRel
|
||||||
from django.db.models.functions import Coalesce, Lower
|
from django.db.models.functions import Coalesce, Lower
|
||||||
from django.db.models.signals import post_save
|
from django.db.models.signals import post_save
|
||||||
from django.http import FileResponse, HttpResponse, JsonResponse
|
from django.http import FileResponse, HttpResponse, JsonResponse, HttpResponseRedirect
|
||||||
from django.shortcuts import get_object_or_404, redirect
|
from django.shortcuts import get_object_or_404, redirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
@ -75,7 +75,7 @@ from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilte
|
|||||||
ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space,
|
ShareLink, ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space,
|
||||||
Step, Storage, Supermarket, SupermarketCategory,
|
Step, Storage, Supermarket, SupermarketCategory,
|
||||||
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
|
SupermarketCategoryRelation, Sync, SyncLog, Unit, UnitConversion,
|
||||||
UserFile, UserPreference, UserSpace, ViewLog)
|
UserFile, UserPreference, UserSpace, ViewLog, FoodProperty)
|
||||||
from cookbook.provider.dropbox import Dropbox
|
from cookbook.provider.dropbox import Dropbox
|
||||||
from cookbook.provider.local import Local
|
from cookbook.provider.local import Local
|
||||||
from cookbook.provider.nextcloud import Nextcloud
|
from cookbook.provider.nextcloud import Nextcloud
|
||||||
@ -104,6 +104,7 @@ from cookbook.serializer import (AccessTokenSerializer, AutomationSerializer,
|
|||||||
UserSerializer, UserSpaceSerializer, ViewLogSerializer)
|
UserSerializer, UserSpaceSerializer, ViewLogSerializer)
|
||||||
from cookbook.views.import_export import get_integration
|
from cookbook.views.import_export import get_integration
|
||||||
from recipes import settings
|
from recipes import settings
|
||||||
|
from recipes.settings import FDC_API_KEY
|
||||||
|
|
||||||
|
|
||||||
class StandardFilterMixin(ViewSetMixin):
|
class StandardFilterMixin(ViewSetMixin):
|
||||||
@ -595,6 +596,54 @@ class FoodViewSet(viewsets.ModelViewSet, TreeMixin):
|
|||||||
created_by=request.user)
|
created_by=request.user)
|
||||||
return Response(content, status=status.HTTP_204_NO_CONTENT)
|
return Response(content, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
@decorators.action(detail=True, methods=['POST'], )
|
||||||
|
def fdc(self, request, pk):
|
||||||
|
"""
|
||||||
|
updates the food with all possible data from the FDC Api
|
||||||
|
if properties with a fdc_id already exist they will be overridden, if existing properties don't have a fdc_id they won't be changed
|
||||||
|
"""
|
||||||
|
food = self.get_object()
|
||||||
|
|
||||||
|
response = requests.get(f'https://api.nal.usda.gov/fdc/v1/food/{food.fdc_id}?api_key={FDC_API_KEY}')
|
||||||
|
if response.status_code == 429:
|
||||||
|
return JsonResponse({'msg', 'API Key Rate Limit reached/exceeded, see https://api.data.gov/docs/rate-limits/ for more information. Configure your key in Tandoor using environment FDC_API_KEY variable.'}, status=429,
|
||||||
|
json_dumps_params={'indent': 4})
|
||||||
|
|
||||||
|
try:
|
||||||
|
data = json.loads(response.content)
|
||||||
|
|
||||||
|
food_property_list = []
|
||||||
|
|
||||||
|
# delete all properties where the property type has a fdc_id as these should be overridden
|
||||||
|
for fp in food.properties.all():
|
||||||
|
if fp.property_type.fdc_id:
|
||||||
|
fp.delete()
|
||||||
|
|
||||||
|
for pt in PropertyType.objects.filter(space=request.space, fdc_id__gte=0).all():
|
||||||
|
if pt.fdc_id:
|
||||||
|
for fn in data['foodNutrients']:
|
||||||
|
if fn['nutrient']['id'] == pt.fdc_id:
|
||||||
|
food_property_list.append(Property(
|
||||||
|
property_type_id=pt.id,
|
||||||
|
property_amount=round(fn['amount'], 2),
|
||||||
|
import_food_id=food.id,
|
||||||
|
space=self.request.space,
|
||||||
|
))
|
||||||
|
|
||||||
|
Property.objects.bulk_create(food_property_list, ignore_conflicts=True, unique_fields=('space', 'import_food_id', 'property_type',))
|
||||||
|
|
||||||
|
property_food_relation_list = []
|
||||||
|
for p in Property.objects.filter(space=self.request.space, import_food_id=food.id).values_list('import_food_id', 'id', ):
|
||||||
|
property_food_relation_list.append(Food.properties.through(food_id=p[0], property_id=p[1]))
|
||||||
|
|
||||||
|
FoodProperty.objects.bulk_create(property_food_relation_list, ignore_conflicts=True, unique_fields=('food_id', 'property_id',))
|
||||||
|
Property.objects.filter(space=self.request.space, import_food_id=food.id).update(import_food_id=None)
|
||||||
|
|
||||||
|
return self.retrieve(request, pk)
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
return JsonResponse({'msg': f'there was an error parsing the FDC data, please check the server logs'}, status=500, json_dumps_params={'indent': 4})
|
||||||
|
|
||||||
def destroy(self, *args, **kwargs):
|
def destroy(self, *args, **kwargs):
|
||||||
try:
|
try:
|
||||||
return (super().destroy(self, *args, **kwargs))
|
return (super().destroy(self, *args, **kwargs))
|
||||||
@ -1454,7 +1503,7 @@ def import_files(request):
|
|||||||
"""
|
"""
|
||||||
limit, msg = above_space_limit(request.space)
|
limit, msg = above_space_limit(request.space)
|
||||||
if limit:
|
if limit:
|
||||||
return Response({'error': msg}, status=status.HTTP_400_BAD_REQUEST)
|
return Response({'error': True, 'msg': _('File is above space limit')}, status=status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
form = ImportForm(request.POST, request.FILES)
|
form = ImportForm(request.POST, request.FILES)
|
||||||
if form.is_valid() and request.FILES != {}:
|
if form.is_valid() and request.FILES != {}:
|
||||||
|
@ -204,6 +204,11 @@ def ingredient_editor(request):
|
|||||||
return render(request, 'ingredient_editor.html', template_vars)
|
return render(request, 'ingredient_editor.html', template_vars)
|
||||||
|
|
||||||
|
|
||||||
|
@group_required('user')
|
||||||
|
def property_editor(request, pk):
|
||||||
|
return render(request, 'property_editor.html', {'recipe_id': pk})
|
||||||
|
|
||||||
|
|
||||||
@group_required('guest')
|
@group_required('guest')
|
||||||
def shopping_settings(request):
|
def shopping_settings(request):
|
||||||
if request.space.demo:
|
if request.space.demo:
|
||||||
@ -220,10 +225,10 @@ def shopping_settings(request):
|
|||||||
if not sp:
|
if not sp:
|
||||||
sp = SearchPreferenceForm(user=request.user)
|
sp = SearchPreferenceForm(user=request.user)
|
||||||
fields_searched = (
|
fields_searched = (
|
||||||
len(search_form.cleaned_data['icontains'])
|
len(search_form.cleaned_data['icontains'])
|
||||||
+ len(search_form.cleaned_data['istartswith'])
|
+ len(search_form.cleaned_data['istartswith'])
|
||||||
+ len(search_form.cleaned_data['trigram'])
|
+ len(search_form.cleaned_data['trigram'])
|
||||||
+ len(search_form.cleaned_data['fulltext'])
|
+ len(search_form.cleaned_data['fulltext'])
|
||||||
)
|
)
|
||||||
if search_form.cleaned_data['preset'] == 'fuzzy':
|
if search_form.cleaned_data['preset'] == 'fuzzy':
|
||||||
sp.search = SearchPreference.SIMPLE
|
sp.search = SearchPreference.SIMPLE
|
||||||
|
50
docs/faq.md
50
docs/faq.md
@ -1,5 +1,5 @@
|
|||||||
There are several questions and issues that come up from time to time, here are some answers:
|
There are several questions and issues that come up from time to time, here are some answers:
|
||||||
please note that the existence of some questions is due the application not being perfect in some parts.
|
please note that the existence of some questions is due the application not being perfect in some parts.
|
||||||
Many of those shortcomings are planned to be fixed in future release but simply could not be addressed yet due to time limits.
|
Many of those shortcomings are planned to be fixed in future release but simply could not be addressed yet due to time limits.
|
||||||
|
|
||||||
## Is there a Tandoor app?
|
## Is there a Tandoor app?
|
||||||
@ -15,7 +15,7 @@ Open Tandoor, click the `add Tandoor to the home screen` message that pops up at
|
|||||||
|
|
||||||
### Desktop browsers
|
### Desktop browsers
|
||||||
|
|
||||||
#### Google Chrome
|
#### Google Chrome
|
||||||
Open Tandoor, open the menu behind the three vertical dots at the top right, select `Install Tandoor Recipes...`
|
Open Tandoor, open the menu behind the three vertical dots at the top right, select `Install Tandoor Recipes...`
|
||||||
|
|
||||||
#### Microsoft Edge
|
#### Microsoft Edge
|
||||||
@ -32,6 +32,17 @@ If you just set up your Tandoor instance and you're having issues like;
|
|||||||
then make sure you have set [all required headers](install/docker.md#required-headers) in your reverse proxy correctly.
|
then make sure you have set [all required headers](install/docker.md#required-headers) in your reverse proxy correctly.
|
||||||
If that doesn't fix it, you can also refer to the appropriate sub section in the [reverse proxy documentation](install/docker.md#reverse-proxy) and verify your general webserver configuration.
|
If that doesn't fix it, you can also refer to the appropriate sub section in the [reverse proxy documentation](install/docker.md#reverse-proxy) and verify your general webserver configuration.
|
||||||
|
|
||||||
|
### Required Headers
|
||||||
|
Navigate to `/system` and review the headers listed in the DEBUG section. At a minimum, if you are using a reverse proxy the headers must match the below conditions.
|
||||||
|
|
||||||
|
| Header | Requirement |
|
||||||
|
| :--- | :---- |
|
||||||
|
| HTTP_HOST:mydomain.tld | The host domain must match the url that you are using to open Tandoor. |
|
||||||
|
| HTTP_X_FORWARDED_HOST:mydomain.tld | The host domain must match the url that you are using to open Tandoor. |
|
||||||
|
| HTTP_X_FORWARDED_PROTO:http(s) | The protocol must match the url you are using to open Tandoor. There must be exactly one protocol listed. |
|
||||||
|
| HTTP_X_SCRIPT_NAME:/subfolder | If you are hosting Tandoor at a subfolder instead of a subdomain this header must exist. |
|
||||||
|
|
||||||
|
|
||||||
## Why am I getting CSRF Errors?
|
## Why am I getting CSRF Errors?
|
||||||
If you are getting CSRF Errors this is most likely due to a reverse proxy not passing the correct headers.
|
If you are getting CSRF Errors this is most likely due to a reverse proxy not passing the correct headers.
|
||||||
|
|
||||||
@ -41,19 +52,22 @@ If you are using a plain ngix you might need `proxy_set_header Host $http_host;`
|
|||||||
Further discussions can be found in this [Issue #518](https://github.com/vabene1111/recipes/issues/518)
|
Further discussions can be found in this [Issue #518](https://github.com/vabene1111/recipes/issues/518)
|
||||||
|
|
||||||
## Why are images not loading?
|
## Why are images not loading?
|
||||||
If images are not loading this might be related to the same issue as the CSRF errors (see above).
|
If images are not loading this might be related to the same issue as the CSRF errors (see above).
|
||||||
A discussion about that can be found at [Issue #452](https://github.com/vabene1111/recipes/issues/452)
|
A discussion about that can be found at [Issue #452](https://github.com/vabene1111/recipes/issues/452)
|
||||||
|
|
||||||
The other common issue is that the recommended nginx container is removed from the deployment stack.
|
The other common issue is that the recommended nginx container is removed from the deployment stack.
|
||||||
If removed, the nginx webserver needs to be replaced by something else that servers the /mediafiles/ directory or
|
If removed, the nginx webserver needs to be replaced by something else that servers the /mediafiles/ directory or
|
||||||
`GUNICORN_MEDIA` needs to be enabled to allow media serving by the application container itself.
|
`GUNICORN_MEDIA` needs to be enabled to allow media serving by the application container itself.
|
||||||
|
|
||||||
|
## Why am I getting an error stating database files are incompatible with server?
|
||||||
|
Your version of Postgres has been upgraded. See [Updating PostgreSQL](https://docs.tandoor.dev/system/updating/#postgresql)
|
||||||
|
|
||||||
|
|
||||||
## Why does the Text/Markdown preview look different than the final recipe?
|
## Why does the Text/Markdown preview look different than the final recipe?
|
||||||
|
|
||||||
Tandoor has always rendered the recipe instructions markdown on the server. This also allows tandoor to implement things like ingredient templating and scaling in text.
|
Tandoor has always rendered the recipe instructions markdown on the server. This also allows tandoor to implement things like ingredient templating and scaling in text.
|
||||||
To make editing easier a markdown editor was added to the frontend with integrated preview as a temporary solution. Since the markdown editor uses a different
|
To make editing easier a markdown editor was added to the frontend with integrated preview as a temporary solution. Since the markdown editor uses a different
|
||||||
specification than the server the preview is different to the final result. It is planned to improve this in the future.
|
specification than the server the preview is different to the final result. It is planned to improve this in the future.
|
||||||
|
|
||||||
The markdown renderer follows this markdown specification https://daringfireball.net/projects/markdown/
|
The markdown renderer follows this markdown specification https://daringfireball.net/projects/markdown/
|
||||||
|
|
||||||
@ -66,18 +80,18 @@ To create a new user click on your name (top right corner) and select 'space set
|
|||||||
|
|
||||||
It is not possible to create users through the admin because users must be assigned a default group and space.
|
It is not possible to create users through the admin because users must be assigned a default group and space.
|
||||||
|
|
||||||
To change a user's space you need to go to the admin and select User Infos.
|
To change a user's space you need to go to the admin and select User Infos.
|
||||||
|
|
||||||
If you use an external auth provider or proxy authentication make sure to specify a default group and space in the
|
If you use an external auth provider or proxy authentication make sure to specify a default group and space in the
|
||||||
environment configuration.
|
environment configuration.
|
||||||
|
|
||||||
## What are spaces?
|
## What are spaces?
|
||||||
Spaces are is a type of feature used to separate one installation of Tandoor into several parts.
|
Spaces are is a type of feature used to separate one installation of Tandoor into several parts.
|
||||||
In technical terms it is a multi-tenant system.
|
In technical terms it is a multi-tenant system.
|
||||||
|
|
||||||
You can compare a space to something like google drive or dropbox.
|
You can compare a space to something like google drive or dropbox.
|
||||||
There is only one installation of the Dropbox system, but it handles multiple users without them noticing each other.
|
There is only one installation of the Dropbox system, but it handles multiple users without them noticing each other.
|
||||||
For Tandoor that means all people that work together on one recipe collection can be in one space.
|
For Tandoor that means all people that work together on one recipe collection can be in one space.
|
||||||
If you want to host the collection of your friends, family, or neighbor you can create a separate space for them (through the admin interface).
|
If you want to host the collection of your friends, family, or neighbor you can create a separate space for them (through the admin interface).
|
||||||
|
|
||||||
Sharing between spaces is currently not possible but is planned for future releases.
|
Sharing between spaces is currently not possible but is planned for future releases.
|
||||||
@ -90,7 +104,7 @@ To reset a lost password if access to the container is lost you need to:
|
|||||||
3. run `python manage.py changepassword <username>` and follow the steps shown.
|
3. run `python manage.py changepassword <username>` and follow the steps shown.
|
||||||
|
|
||||||
## How can I add an admin user?
|
## How can I add an admin user?
|
||||||
To create a superuser you need to
|
To create a superuser you need to
|
||||||
|
|
||||||
1. execute into the container using `docker-compose exec web_recipes sh`
|
1. execute into the container using `docker-compose exec web_recipes sh`
|
||||||
2. activate the virtual environment `source venv/bin/activate`
|
2. activate the virtual environment `source venv/bin/activate`
|
||||||
@ -98,10 +112,10 @@ To create a superuser you need to
|
|||||||
|
|
||||||
|
|
||||||
## Why cant I get support for my manual setup?
|
## Why cant I get support for my manual setup?
|
||||||
Even tough I would love to help everyone get tandoor up and running I have only so much time
|
Even tough I would love to help everyone get tandoor up and running I have only so much time
|
||||||
that I can spend on this project besides work, family and other life things.
|
that I can spend on this project besides work, family and other life things.
|
||||||
Due to the countless problems that can occur when manually installing I simply do not have
|
Due to the countless problems that can occur when manually installing I simply do not have
|
||||||
the time to help solving each one.
|
the time to help solving each one.
|
||||||
|
|
||||||
You can install Tandoor manually but please do not expect me or anyone to help you with that.
|
You can install Tandoor manually but please do not expect me or anyone to help you with that.
|
||||||
As a general advice: If you do it manually do NOT change anything at first and slowly work yourself
|
As a general advice: If you do it manually do NOT change anything at first and slowly work yourself
|
||||||
@ -120,4 +134,4 @@ Postgres requires manual intervention when updating from one major version to an
|
|||||||
|
|
||||||
If anything fails, go back to the old postgres version and data directory and try again.
|
If anything fails, go back to the old postgres version and data directory and try again.
|
||||||
|
|
||||||
There are many articles and tools online that might provide a good starting point to help you upgrade [1](https://thomasbandt.com/postgres-docker-major-version-upgrade), [2](https://github.com/tianon/docker-postgres-upgrade), [3](https://github.com/vabene1111/DockerPostgresBackups).
|
There are many articles and tools online that might provide a good starting point to help you upgrade [1](https://thomasbandt.com/postgres-docker-major-version-upgrade), [2](https://github.com/tianon/docker-postgres-upgrade), [3](https://github.com/vabene1111/DockerPostgresBackups).
|
||||||
|
@ -6,6 +6,12 @@ It is possible to install this application using many different Docker configura
|
|||||||
|
|
||||||
Please read the instructions on each example carefully and decide if this is the way for you.
|
Please read the instructions on each example carefully and decide if this is the way for you.
|
||||||
|
|
||||||
|
## **DockSTARTer**
|
||||||
|
|
||||||
|
The main goal of [DockSTARTer](https://dockstarter.com/) is to make it quick and easy to get up and running with Docker.
|
||||||
|
You may choose to rely on DockSTARTer for various changes to your Docker system or use DockSTARTer as a stepping stone and learn to do more advanced configurations.
|
||||||
|
Follow the guide for installing DockSTARTer and then run `ds` then select 'Configuration' and 'Select Apps' to get Tandoor up and running quickly and easily.
|
||||||
|
|
||||||
## **Docker**
|
## **Docker**
|
||||||
|
|
||||||
The docker image (`vabene1111/recipes`) simply exposes the application on the container's port `8080`.
|
The docker image (`vabene1111/recipes`) simply exposes the application on the container's port `8080`.
|
||||||
@ -110,7 +116,7 @@ in combination with [jrcs's letsencrypt companion](https://hub.docker.com/r/jrcs
|
|||||||
Please refer to the appropriate documentation on how to setup the reverse proxy and networks.
|
Please refer to the appropriate documentation on how to setup the reverse proxy and networks.
|
||||||
|
|
||||||
!!! warning "Adjust client_max_body_size"
|
!!! warning "Adjust client_max_body_size"
|
||||||
By using jwilder's Nginx-proxy, uploads will be restricted to 1 MB file size. This can be resolved by adjusting the ```client_max_body_size``` variable in the jwilder nginx configuration.
|
By using jwilder's Nginx-proxy, uploads will be restricted to 1 MB file size. This can be resolved by adjusting the ```client_max_body_size``` variable in the jwilder nginx configuration.
|
||||||
|
|
||||||
Remember to add the appropriate environment variables to the `.env` file:
|
Remember to add the appropriate environment variables to the `.env` file:
|
||||||
|
|
||||||
@ -360,11 +366,11 @@ follow these instructions:
|
|||||||
### Sub Path nginx config
|
### Sub Path nginx config
|
||||||
|
|
||||||
If hosting under a sub-path you might want to change the default nginx config (which gets mounted through the named volume from the application container into the nginx container)
|
If hosting under a sub-path you might want to change the default nginx config (which gets mounted through the named volume from the application container into the nginx container)
|
||||||
with the following config.
|
with the following config.
|
||||||
|
|
||||||
```nginx
|
```nginx
|
||||||
location /my_app { # change to subfolder name
|
location /my_app { # change to subfolder name
|
||||||
include /config/nginx/proxy.conf;
|
include /config/nginx/proxy.conf;
|
||||||
proxy_pass https://mywebapp.com/; # change to your host name:port
|
proxy_pass https://mywebapp.com/; # change to your host name:port
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
@ -8,7 +8,7 @@ downloaded and restored through the web interface.
|
|||||||
When developing a new backup strategy, make sure to also test the restore process!
|
When developing a new backup strategy, make sure to also test the restore process!
|
||||||
|
|
||||||
## Database
|
## Database
|
||||||
Please use any standard way of backing up your database. For most systems this can be achieved by using a dump
|
Please use any standard way of backing up your database. For most systems this can be achieved by using a dump
|
||||||
command that will create an SQL file with all the required data.
|
command that will create an SQL file with all the required data.
|
||||||
|
|
||||||
Please refer to your Database System documentation.
|
Please refer to your Database System documentation.
|
||||||
@ -18,7 +18,7 @@ It is **neither** well tested nor documented so use at your own risk.
|
|||||||
I would recommend using it only as a starting place for your own backup strategy.
|
I would recommend using it only as a starting place for your own backup strategy.
|
||||||
|
|
||||||
## Mediafiles
|
## Mediafiles
|
||||||
The only Data this application stores apart from the database are the media files (e.g. images) used in your
|
The only Data this application stores apart from the database are the media files (e.g. images) used in your
|
||||||
recipes.
|
recipes.
|
||||||
|
|
||||||
They can be found in the mediafiles mounted directory (depending on your installation).
|
They can be found in the mediafiles mounted directory (depending on your installation).
|
||||||
@ -56,3 +56,23 @@ You can now export recipes from Tandoor using the export function. This method r
|
|||||||
Import:
|
Import:
|
||||||
Go to Import > from app > tandoor and select the zip file you want to import from.
|
Go to Import > from app > tandoor and select the zip file you want to import from.
|
||||||
|
|
||||||
|
## Backing up using the pgbackup container
|
||||||
|
You can add [pgbackup](https://hub.docker.com/r/prodrigestivill/postgres-backup-local) to manage the scheduling and automatic backup of your postgres database.
|
||||||
|
Modify the below to match your environment and add it to your `docker-compose.yml`
|
||||||
|
|
||||||
|
``` yaml
|
||||||
|
pgbackup:
|
||||||
|
container_name: pgbackup
|
||||||
|
environment:
|
||||||
|
BACKUP_KEEP_DAYS: "8"
|
||||||
|
BACKUP_KEEP_MONTHS: "6"
|
||||||
|
BACKUP_KEEP_WEEKS: "4"
|
||||||
|
POSTGRES_EXTRA_OPTS: -Z6 --schema=public --blobs
|
||||||
|
SCHEDULE: '@daily'
|
||||||
|
# Note: the tag must match the version of postgres you are using
|
||||||
|
image: prodrigestivill/postgres-backup-local:15
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- backups/postgres:/backups
|
||||||
|
```
|
||||||
|
You can manually initiate a backup by running `docker exec -it pgbackup ./backup.sh`
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
The Updating process depends on your chosen method of [installation](/install/docker)
|
The Updating process depends on your chosen method of [installation](/install/docker)
|
||||||
|
|
||||||
While intermediate updates can be skipped when updating please make sure to
|
While intermediate updates can be skipped when updating please make sure to
|
||||||
**read the release notes** in case some special action is required to update.
|
**read the release notes** in case some special action is required to update.
|
||||||
|
|
||||||
## Docker
|
## Docker
|
||||||
@ -16,7 +16,79 @@ For all setups using Docker the updating process look something like this
|
|||||||
For all setups using a manual installation updates usually involve downloading the latest source code from GitHub.
|
For all setups using a manual installation updates usually involve downloading the latest source code from GitHub.
|
||||||
After that make sure to run:
|
After that make sure to run:
|
||||||
|
|
||||||
1. `manage.py collectstatic`
|
1. `pip install -r requirements.txt`
|
||||||
2. `manage.py migrate`
|
2. `manage.py collectstatic`
|
||||||
|
3. `manage.py migrate`
|
||||||
|
4. `cd ./vue`
|
||||||
|
5. `yarn install`
|
||||||
|
6. `yarn build`
|
||||||
|
|
||||||
To apply all new migrations and collect new static files.
|
To install latest libraries, apply all new migrations and collect new static files.
|
||||||
|
|
||||||
|
## PostgreSQL
|
||||||
|
|
||||||
|
Postgres does not automatically upgrade database files when you change versions and requires manual intervention.
|
||||||
|
One option is to manually [backup/restore](https://docs.tandoor.dev/system/updating/#postgresql) the database.
|
||||||
|
|
||||||
|
A full list of options to upgrade a database provide in the [official PostgreSQL documentation](https://www.postgresql.org/docs/current/upgrading.html).
|
||||||
|
|
||||||
|
1. Collect information about your environment.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
grep -E 'POSTGRES|DATABASE' ~/.docker/compose/.env
|
||||||
|
docker ps -a --format 'table {{.ID}}\t{{.Names}}\t{{.Image}}\t{{.Status}}' | awk 'NR == 1 || /postgres/ || /recipes/'
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Export the tandoor database
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
docker exec -t {{database_container}} pg_dumpall -U {{djangouser}} > ~/tandoor.sql
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Stop the postgres container
|
||||||
|
``` bash
|
||||||
|
docker stop {{database_container}} {{tandoor_container}}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Rename the tandoor volume
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
sudo mv -R ~/.docker/compose/postgres ~/.docker/compose/postgres.old
|
||||||
|
```
|
||||||
|
|
||||||
|
5. Update image tag on postgres container.
|
||||||
|
|
||||||
|
``` yaml
|
||||||
|
db_recipes:
|
||||||
|
restart: always
|
||||||
|
image: postgres:16-alpine
|
||||||
|
volumes:
|
||||||
|
- ./postgresql:/var/lib/postgresql/data
|
||||||
|
env_file:
|
||||||
|
- ./.env
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Pull and rebuild container.
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
docker-compose pull && docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
7. Import the database export
|
||||||
|
|
||||||
|
``` bash
|
||||||
|
cat ~/tandoor.sql | sudo docker exec -i {{database_container}} psql postgres -U {{djangouser}}
|
||||||
|
```
|
||||||
|
8. Install postgres extensions
|
||||||
|
``` bash
|
||||||
|
docker exec -it {{database_container}} psql
|
||||||
|
```
|
||||||
|
then
|
||||||
|
``` psql
|
||||||
|
CREATE EXTENSION IF NOT EXISTS unaccent;
|
||||||
|
CREATE EXTENSION IF NOT EXISTS pg_trgm;
|
||||||
|
```
|
||||||
|
|
||||||
|
If anything fails, go back to the old postgres version and data directory and try again.
|
||||||
|
|
||||||
|
There are many articles and tools online that might provide a good starting point to help you upgrade [1](https://thomasbandt.com/postgres-docker-major-version-upgrade), [2](https://github.com/tianon/docker-postgres-upgrade), [3](https://github.com/vabene1111/DockerPostgresBackups).
|
||||||
|
@ -89,7 +89,7 @@ DJANGO_TABLES2_PAGE_RANGE = 8
|
|||||||
HCAPTCHA_SITEKEY = os.getenv('HCAPTCHA_SITEKEY', '')
|
HCAPTCHA_SITEKEY = os.getenv('HCAPTCHA_SITEKEY', '')
|
||||||
HCAPTCHA_SECRET = os.getenv('HCAPTCHA_SECRET', '')
|
HCAPTCHA_SECRET = os.getenv('HCAPTCHA_SECRET', '')
|
||||||
|
|
||||||
FDA_API_KEY = os.getenv('FDA_API_KEY', 'DEMO_KEY')
|
FDC_API_KEY = os.getenv('FDC_API_KEY', 'DEMO_KEY')
|
||||||
|
|
||||||
SHARING_ABUSE = bool(int(os.getenv('SHARING_ABUSE', False)))
|
SHARING_ABUSE = bool(int(os.getenv('SHARING_ABUSE', False)))
|
||||||
SHARING_LIMIT = int(os.getenv('SHARING_LIMIT', 0))
|
SHARING_LIMIT = int(os.getenv('SHARING_LIMIT', 0))
|
||||||
@ -350,7 +350,7 @@ WSGI_APPLICATION = 'recipes.wsgi.application'
|
|||||||
# Load settings from env files
|
# Load settings from env files
|
||||||
if os.getenv('DATABASE_URL'):
|
if os.getenv('DATABASE_URL'):
|
||||||
match = re.match(
|
match = re.match(
|
||||||
r'(?P<schema>\w+):\/\/(?:(?P<user>[\w\d_-]+)(?::(?P<password>[^@]+))?@)?(?P<host>[^:/]+)(?:(?P<port>\d+))?(?:/(?P<database>[\w\d/._-]+))?',
|
r'(?P<schema>\w+):\/\/(?:(?P<user>[\w\d_-]+)(?::(?P<password>[^@]+))?@)?(?P<host>[^:/]+)(?::(?P<port>\d+))?(?:/(?P<database>[\w\d/._-]+))?',
|
||||||
os.getenv('DATABASE_URL')
|
os.getenv('DATABASE_URL')
|
||||||
)
|
)
|
||||||
settings = match.groupdict()
|
settings = match.groupdict()
|
||||||
@ -450,7 +450,11 @@ for p in PLUGINS:
|
|||||||
|
|
||||||
LANGUAGE_CODE = 'en'
|
LANGUAGE_CODE = 'en'
|
||||||
|
|
||||||
TIME_ZONE = os.getenv('TIMEZONE') if os.getenv('TIMEZONE') else 'Europe/Berlin'
|
if os.getenv('TIMEZONE') is not None:
|
||||||
|
print('DEPRECATION WARNING: Environment var "TIMEZONE" is deprecated. Please use "TZ" instead.')
|
||||||
|
TIME_ZONE = os.getenv('TIMEZONE') if os.getenv('TIMEZONE') else 'Europe/Berlin'
|
||||||
|
else:
|
||||||
|
TIME_ZONE = os.getenv('TZ') if os.getenv('TZ') else 'Europe/Berlin'
|
||||||
|
|
||||||
USE_I18N = True
|
USE_I18N = True
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
Django==4.2.7
|
Django==4.2.7
|
||||||
cryptography===41.0.4
|
cryptography===41.0.6
|
||||||
django-annoying==0.10.6
|
django-annoying==0.10.6
|
||||||
django-autocomplete-light==3.9.4
|
django-autocomplete-light==3.9.4
|
||||||
django-cleanup==8.0.0
|
django-cleanup==8.0.0
|
||||||
@ -32,7 +32,7 @@ git+https://github.com/BITSOLVER/django-js-reverse@071e304fd600107bc64bbde6f2491
|
|||||||
django-allauth==0.54.0
|
django-allauth==0.54.0
|
||||||
recipe-scrapers==14.52.0
|
recipe-scrapers==14.52.0
|
||||||
django-scopes==2.0.0
|
django-scopes==2.0.0
|
||||||
pytest==7.3.1
|
pytest==7.4.3
|
||||||
pytest-django==4.6.0
|
pytest-django==4.6.0
|
||||||
django-treebeard==4.7
|
django-treebeard==4.7
|
||||||
django-cors-headers==4.2.0
|
django-cors-headers==4.2.0
|
||||||
|
@ -669,8 +669,7 @@ export default {
|
|||||||
if (url !== '') {
|
if (url !== '') {
|
||||||
this.failed_imports.push(url)
|
this.failed_imports.push(url)
|
||||||
}
|
}
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_IMPORT, err)
|
||||||
throw "Load Recipe Error"
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -713,8 +712,7 @@ export default {
|
|||||||
axios.post(resolveDjangoUrl('view_import'), formData, {headers: {'Content-Type': 'multipart/form-data'}}).then((response) => {
|
axios.post(resolveDjangoUrl('view_import'), formData, {headers: {'Content-Type': 'multipart/form-data'}}).then((response) => {
|
||||||
window.location.href = resolveDjangoUrl('view_import_response', response.data['import_id'])
|
window.location.href = resolveDjangoUrl('view_import_response', response.data['import_id'])
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(err)
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_IMPORT, err)
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_CREATE)
|
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -744,8 +744,12 @@ having to override as much.
|
|||||||
|
|
||||||
.theme-default .cv-item.continued::before,
|
.theme-default .cv-item.continued::before,
|
||||||
.theme-default .cv-item.toBeContinued::after {
|
.theme-default .cv-item.toBeContinued::after {
|
||||||
|
/*
|
||||||
|
removed because it breaks a line and would increase item size https://github.com/TandoorRecipes/recipes/issues/2678
|
||||||
|
|
||||||
content: " \21e2 ";
|
content: " \21e2 ";
|
||||||
color: #999;
|
color: #999;
|
||||||
|
*/
|
||||||
}
|
}
|
||||||
|
|
||||||
.theme-default .cv-item.toBeContinued {
|
.theme-default .cv-item.toBeContinued {
|
||||||
|
@ -186,10 +186,10 @@ export default {
|
|||||||
case "ingredient-editor": {
|
case "ingredient-editor": {
|
||||||
let url = resolveDjangoUrl("view_ingredient_editor")
|
let url = resolveDjangoUrl("view_ingredient_editor")
|
||||||
if (this.this_model === this.Models.FOOD) {
|
if (this.this_model === this.Models.FOOD) {
|
||||||
window.location.href = url + '?food_id=' + e.source.id
|
window.open(url + '?food_id=' + e.source.id, "_blank");
|
||||||
}
|
}
|
||||||
if (this.this_model === this.Models.UNIT) {
|
if (this.this_model === this.Models.UNIT) {
|
||||||
window.location.href = url + '?unit_id=' + e.source.id
|
window.open(url + '?unit_id=' + e.source.id, "_blank");
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
227
vue/src/apps/PropertyEditorView/PropertyEditorView.vue
Normal file
227
vue/src/apps/PropertyEditorView/PropertyEditorView.vue
Normal file
@ -0,0 +1,227 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div id="app">
|
||||||
|
<div>
|
||||||
|
<div class="row" v-if="recipe" style="max-height: 10vh">
|
||||||
|
|
||||||
|
<div class="col col-8">
|
||||||
|
<h2><a :href="resolveDjangoUrl('view_recipe', recipe.id)">{{ recipe.name }}</a></h2>
|
||||||
|
{{ recipe.description }}
|
||||||
|
<keywords-component :recipe="recipe"></keywords-component>
|
||||||
|
</div>
|
||||||
|
<div class="col col-4" v-if="recipe.image">
|
||||||
|
<img style="max-height: 10vh" class="img-thumbnail float-right" :src="recipe.image">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<div class="row mt-3">
|
||||||
|
<div class="col col-12">
|
||||||
|
<b-button variant="success" href="https://fdc.nal.usda.gov/index.html" target="_blank"><i class="fas fa-external-link-alt"></i> {{$t('FDC_Search')}}</b-button>
|
||||||
|
|
||||||
|
<table class="table table-sm table-bordered table-responsive mt-2 pb-5">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<td>{{ $t('Name') }}</td>
|
||||||
|
<td>FDC</td>
|
||||||
|
<td>{{ $t('Properties_Food_Amount') }}</td>
|
||||||
|
<td>{{ $t('Properties_Food_Unit') }}</td>
|
||||||
|
<td v-for="pt in property_types" v-bind:key="pt.id">
|
||||||
|
<b-button variant="primary" @click="editing_property_type = pt" class="btn-block">{{ pt.name }}
|
||||||
|
<span v-if="pt.unit !== ''">({{ pt.unit }}) </span> <br/>
|
||||||
|
<b-badge variant="light" ><i class="fas fa-sort-amount-down-alt"></i> {{ pt.order}}</b-badge>
|
||||||
|
<b-badge variant="success" v-if="pt.fdc_id > 0" class="mt-2" v-b-tooltip.hover :title="$t('property_type_fdc_hint')"><i class="fas fa-check"></i> FDC</b-badge>
|
||||||
|
<b-badge variant="warning" v-if="pt.fdc_id < 1" class="mt-2" v-b-tooltip.hover :title="$t('property_type_fdc_hint')"><i class="fas fa-times"></i> FDC</b-badge>
|
||||||
|
</b-button>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<b-button variant="success" @click="new_property_type = true"><i class="fas fa-plus"></i></b-button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="f in this.foods" v-bind:key="f.id">
|
||||||
|
<td>
|
||||||
|
{{ f.name }}
|
||||||
|
</td>
|
||||||
|
<td style="width: 15em;">
|
||||||
|
<b-input-group>
|
||||||
|
<b-form-input v-model="f.fdc_id" type="number" @change="updateFood(f)" :disabled="f.loading"></b-form-input>
|
||||||
|
<b-input-group-append>
|
||||||
|
<b-button variant="success" @click="updateFoodFromFDC(f)" :disabled="f.loading"><i class="fas fa-sync-alt" :class="{'fa-spin': loading}"></i></b-button>
|
||||||
|
<b-button variant="info" :href="`https://fdc.nal.usda.gov/fdc-app.html#/food-details/${f.fdc_id}`" :disabled="f.fdc_id < 1" target="_blank"><i class="fas fa-external-link-alt"></i></b-button>
|
||||||
|
</b-input-group-append>
|
||||||
|
</b-input-group>
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td style="width: 5em; ">
|
||||||
|
<b-input v-model="f.properties_food_amount" type="number" @change="updateFood(f)" :disabled="f.loading"></b-input>
|
||||||
|
</td>
|
||||||
|
<td style="width: 11em;">
|
||||||
|
<generic-multiselect
|
||||||
|
@change="f.properties_food_unit = $event.val; updateFood(f)"
|
||||||
|
:initial_single_selection="f.properties_food_unit"
|
||||||
|
label="name" :model="Models.UNIT"
|
||||||
|
:multiple="false"
|
||||||
|
:disabled="f.loading"/>
|
||||||
|
</td>
|
||||||
|
<td v-for="p in f.properties" v-bind:key="`${f.id}_${p.property_type.id}`">
|
||||||
|
<b-input-group>
|
||||||
|
<b-form-input v-model="p.property_amount" type="number" :disabled="f.loading" v-b-tooltip.focus :title="p.property_type.name" @change="updateFood(f)"></b-form-input>
|
||||||
|
</b-input-group>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<generic-modal-form
|
||||||
|
:show="editing_property_type !== null"
|
||||||
|
:model="Models.PROPERTY_TYPE"
|
||||||
|
:action="Actions.UPDATE"
|
||||||
|
:item1="editing_property_type"
|
||||||
|
@finish-action="editing_property_type = null; loadData()">
|
||||||
|
</generic-modal-form>
|
||||||
|
|
||||||
|
<generic-modal-form
|
||||||
|
:show="new_property_type"
|
||||||
|
:model="Models.PROPERTY_TYPE"
|
||||||
|
:action="Actions.CREATE"
|
||||||
|
@finish-action="new_property_type = false; loadData()">
|
||||||
|
</generic-modal-form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Vue from "vue"
|
||||||
|
import {BootstrapVue} from "bootstrap-vue"
|
||||||
|
|
||||||
|
import "bootstrap-vue/dist/bootstrap-vue.css"
|
||||||
|
import {ApiMixin, resolveDjangoUrl, StandardToasts} from "@/utils/utils";
|
||||||
|
import axios from "axios";
|
||||||
|
import BetaWarning from "@/components/BetaWarning.vue";
|
||||||
|
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||||
|
import GenericMultiselect from "@/components/GenericMultiselect.vue";
|
||||||
|
import GenericModalForm from "@/components/Modals/GenericModalForm.vue";
|
||||||
|
import KeywordsComponent from "@/components/KeywordsComponent.vue";
|
||||||
|
|
||||||
|
|
||||||
|
Vue.use(BootstrapVue)
|
||||||
|
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "PropertyEditorView",
|
||||||
|
mixins: [ApiMixin],
|
||||||
|
components: {KeywordsComponent, GenericModalForm, GenericMultiselect},
|
||||||
|
computed: {},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
recipe: null,
|
||||||
|
property_types: [],
|
||||||
|
editing_property_type: null,
|
||||||
|
new_property_type: false,
|
||||||
|
loading: false,
|
||||||
|
foods: [],
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||||
|
|
||||||
|
this.loadData();
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
resolveDjangoUrl,
|
||||||
|
loadData: function () {
|
||||||
|
let apiClient = new ApiApiFactory()
|
||||||
|
|
||||||
|
apiClient.listPropertyTypes().then(result => {
|
||||||
|
this.property_types = result.data
|
||||||
|
|
||||||
|
apiClient.retrieveRecipe(window.RECIPE_ID).then(result => {
|
||||||
|
this.recipe = result.data
|
||||||
|
|
||||||
|
this.foods = []
|
||||||
|
|
||||||
|
this.recipe.steps.forEach(s => {
|
||||||
|
s.ingredients.forEach(i => {
|
||||||
|
if (this.foods.filter(x => (x.id === i.food.id)).length === 0) {
|
||||||
|
this.foods.push(this.buildFood(i.food))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
this.loading = false;
|
||||||
|
}).catch((err) => {
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
|
||||||
|
})
|
||||||
|
}).catch((err) => {
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_FETCH, err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
buildFood: function (food) {
|
||||||
|
/**
|
||||||
|
* Prepare food for display in grid by making sure the food properties are in the same order as property_types and that no types are missing
|
||||||
|
* */
|
||||||
|
|
||||||
|
let existing_properties = {}
|
||||||
|
food.properties.forEach(fp => {
|
||||||
|
existing_properties[fp.property_type.id] = fp
|
||||||
|
})
|
||||||
|
|
||||||
|
let food_properties = []
|
||||||
|
this.property_types.forEach(pt => {
|
||||||
|
let new_food_property = {
|
||||||
|
property_type: pt,
|
||||||
|
property_amount: 0,
|
||||||
|
}
|
||||||
|
if (pt.id in existing_properties) {
|
||||||
|
new_food_property = existing_properties[pt.id]
|
||||||
|
}
|
||||||
|
food_properties.push(new_food_property)
|
||||||
|
})
|
||||||
|
|
||||||
|
this.$set(food, 'loading', false)
|
||||||
|
|
||||||
|
food.properties = food_properties
|
||||||
|
|
||||||
|
return food
|
||||||
|
},
|
||||||
|
spliceInFood: function (food) {
|
||||||
|
/**
|
||||||
|
* replace food in foods list, for example after updates from the server
|
||||||
|
*/
|
||||||
|
this.foods = this.foods.map(f => (f.id === food.id) ? food : f)
|
||||||
|
|
||||||
|
},
|
||||||
|
updateFood: function (food) {
|
||||||
|
let apiClient = new ApiApiFactory()
|
||||||
|
apiClient.partialUpdateFood(food.id, food).then(result => {
|
||||||
|
this.spliceInFood(this.buildFood(result.data))
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
||||||
|
}).catch((err) => {
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
updateFoodFromFDC: function (food) {
|
||||||
|
food.loading = true;
|
||||||
|
let apiClient = new ApiApiFactory()
|
||||||
|
|
||||||
|
apiClient.fdcFood(food.id).then(result => {
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
||||||
|
this.spliceInFood(this.buildFood(result.data))
|
||||||
|
}).catch((err) => {
|
||||||
|
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
||||||
|
food.loading = false;
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
|
||||||
|
</style>
|
22
vue/src/apps/PropertyEditorView/main.js
Normal file
22
vue/src/apps/PropertyEditorView/main.js
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import Vue from 'vue'
|
||||||
|
import App from './PropertyEditorView.vue'
|
||||||
|
import i18n from '@/i18n'
|
||||||
|
import {createPinia, PiniaVuePlugin} from "pinia";
|
||||||
|
|
||||||
|
Vue.config.productionTip = false
|
||||||
|
|
||||||
|
// TODO move this and other default stuff to centralized JS file (verify nothing breaks)
|
||||||
|
let publicPath = localStorage.STATIC_URL + 'vue/'
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
publicPath = 'http://localhost:8080/'
|
||||||
|
}
|
||||||
|
export default __webpack_public_path__ = publicPath // eslint-disable-line
|
||||||
|
|
||||||
|
Vue.use(PiniaVuePlugin)
|
||||||
|
const pinia = createPinia()
|
||||||
|
|
||||||
|
new Vue({
|
||||||
|
pinia,
|
||||||
|
i18n,
|
||||||
|
render: h => h(App),
|
||||||
|
}).$mount('#app')
|
@ -2,34 +2,7 @@
|
|||||||
|
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<div>
|
<div>
|
||||||
<h2 v-if="recipe">{{ recipe.name}}</h2>
|
|
||||||
|
|
||||||
<table class="table table-sm table-bordered">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<td>{{ $t('Name') }}</td>
|
|
||||||
<td v-for="pt in property_types" v-bind:key="pt.id">{{ pt.name }}
|
|
||||||
<input type="text" v-model="pt.unit" @change="updatePropertyType(pt)">
|
|
||||||
<input v-model="pt.fdc_id" type="number" placeholder="FDC ID" @change="updatePropertyType(pt)"></td>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="f in this.foods" v-bind:key="f.food.id">
|
|
||||||
<td>
|
|
||||||
{{ f.food.name }}
|
|
||||||
{{ $t('Property') }} / <input type="number" v-model="f.food.properties_food_amount" @change="updateFood(f.food)">
|
|
||||||
<generic-multiselect
|
|
||||||
@change="f.food.properties_food_unit = $event.val; updateFood(f.food)"
|
|
||||||
:initial_selection="f.food.properties_food_unit"
|
|
||||||
label="name" :model="Models.UNIT"
|
|
||||||
:multiple="false"/>
|
|
||||||
<input v-model="f.food.fdc_id" placeholder="FDC ID">
|
|
||||||
<button>Load FDC</button>
|
|
||||||
</td>
|
|
||||||
<td v-for="p in f.properties" v-bind:key="`${f.id}_${p.property_type.id}`"><input type="number" v-model="p.property_amount"> {{ p.property_type.unit }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@ -45,6 +18,7 @@ import axios from "axios";
|
|||||||
import BetaWarning from "@/components/BetaWarning.vue";
|
import BetaWarning from "@/components/BetaWarning.vue";
|
||||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||||
import GenericMultiselect from "@/components/GenericMultiselect.vue";
|
import GenericMultiselect from "@/components/GenericMultiselect.vue";
|
||||||
|
import GenericModalForm from "@/components/Modals/GenericModalForm.vue";
|
||||||
|
|
||||||
|
|
||||||
Vue.use(BootstrapVue)
|
Vue.use(BootstrapVue)
|
||||||
@ -53,66 +27,16 @@ Vue.use(BootstrapVue)
|
|||||||
export default {
|
export default {
|
||||||
name: "TestView",
|
name: "TestView",
|
||||||
mixins: [ApiMixin],
|
mixins: [ApiMixin],
|
||||||
components: {GenericMultiselect},
|
components: {},
|
||||||
computed: {
|
computed: {},
|
||||||
foods: function () {
|
|
||||||
let foods = []
|
|
||||||
if (this.recipe !== null && this.property_types !== []) {
|
|
||||||
this.recipe.steps.forEach(s => {
|
|
||||||
s.ingredients.forEach(i => {
|
|
||||||
let food = {food: i.food, properties: {}}
|
|
||||||
|
|
||||||
this.property_types.forEach(pt => {
|
|
||||||
food.properties[pt.id] = {changed: false, property_amount: 0, property_type: pt}
|
|
||||||
})
|
|
||||||
i.food.properties.forEach(fp => {
|
|
||||||
food.properties[fp.property_type.id] = {changed: false, property_amount: fp.property_amount, property_type: fp.property_type}
|
|
||||||
})
|
|
||||||
foods.push(food)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
return foods
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {}
|
||||||
recipe: null,
|
|
||||||
property_types: []
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$i18n.locale = window.CUSTOM_LOCALE
|
this.$i18n.locale = window.CUSTOM_LOCALE
|
||||||
|
|
||||||
let apiClient = new ApiApiFactory()
|
|
||||||
|
|
||||||
apiClient.retrieveRecipe("112").then(result => {
|
|
||||||
this.recipe = result.data
|
|
||||||
})
|
|
||||||
|
|
||||||
apiClient.listPropertyTypes().then(result => {
|
|
||||||
this.property_types = result.data
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
updateFood: function (food) {
|
|
||||||
let apiClient = new ApiApiFactory()
|
|
||||||
apiClient.partialUpdateFood(food.id, food).then(result => {
|
|
||||||
//TODO handle properly
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
|
||||||
}).catch((err) => {
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
updatePropertyType: function (pt) {
|
|
||||||
let apiClient = new ApiApiFactory()
|
|
||||||
apiClient.partialUpdatePropertyType(pt.id, pt).then(result => {
|
|
||||||
//TODO handle properly
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.SUCCESS_UPDATE)
|
|
||||||
}).catch((err) => {
|
|
||||||
StandardToasts.makeStandardToast(this, StandardToasts.FAIL_UPDATE, err)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -33,11 +33,11 @@
|
|||||||
|
|
||||||
<h5><i class="fas fa-database"></i> {{ $t('Properties') }}</h5>
|
<h5><i class="fas fa-database"></i> {{ $t('Properties') }}</h5>
|
||||||
|
|
||||||
<b-form-group :label="$t('Properties Food Amount')" description=""> <!-- TODO localize -->
|
<b-form-group :label="$t('Properties_Food_Amount')" description="">
|
||||||
<b-form-input v-model="food.properties_food_amount"></b-form-input>
|
<b-form-input v-model="food.properties_food_amount"></b-form-input>
|
||||||
</b-form-group>
|
</b-form-group>
|
||||||
|
|
||||||
<b-form-group :label="$t('Properties Food Unit')" description=""> <!-- TODO localize -->
|
<b-form-group :label="$t('Properties_Food_Unit')" description="">
|
||||||
<generic-multiselect
|
<generic-multiselect
|
||||||
@change="food.properties_food_unit = $event.val;"
|
@change="food.properties_food_unit = $event.val;"
|
||||||
:model="Models.UNIT"
|
:model="Models.UNIT"
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
@input="selectionChanged"
|
@input="selectionChanged"
|
||||||
@tag="addNew"
|
@tag="addNew"
|
||||||
@open="selectOpened()"
|
@open="selectOpened()"
|
||||||
|
:disabled="disabled"
|
||||||
>
|
>
|
||||||
</multiselect>
|
</multiselect>
|
||||||
</template>
|
</template>
|
||||||
@ -74,6 +75,7 @@ export default {
|
|||||||
allow_create: { type: Boolean, default: false },
|
allow_create: { type: Boolean, default: false },
|
||||||
create_placeholder: { type: String, default: "You Forgot to Add a Tag Placeholder" },
|
create_placeholder: { type: String, default: "You Forgot to Add a Tag Placeholder" },
|
||||||
clear: { type: Number },
|
clear: { type: Number },
|
||||||
|
disabled: {type: Boolean, default: false, },
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
initial_selection: function (newVal, oldVal) {
|
initial_selection: function (newVal, oldVal) {
|
||||||
|
@ -25,6 +25,12 @@ export default {
|
|||||||
},
|
},
|
||||||
mounted() {
|
mounted() {
|
||||||
this.new_value = this.value
|
this.new_value = this.value
|
||||||
|
|
||||||
|
if (this.new_value === "") { // if the selection is empty but the options are of type number, set to 0 instead of ""
|
||||||
|
if (typeof this.options[0]['value'] === 'number') {
|
||||||
|
this.new_value = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
new_value: function () {
|
new_value: function () {
|
||||||
|
@ -14,7 +14,7 @@ export default {
|
|||||||
props: {
|
props: {
|
||||||
field: { type: String, default: "You Forgot To Set Field Name" },
|
field: { type: String, default: "You Forgot To Set Field Name" },
|
||||||
label: { type: String, default: "Text Field" },
|
label: { type: String, default: "Text Field" },
|
||||||
value: { type: String, default: "" },
|
value: { type: Number, default: 0 },
|
||||||
placeholder: { type: Number, default: 0 },
|
placeholder: { type: Number, default: 0 },
|
||||||
help: { type: String, default: undefined },
|
help: { type: String, default: undefined },
|
||||||
subtitle: { type: String, default: undefined },
|
subtitle: { type: String, default: undefined },
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
|
|
||||||
|
|
||||||
<table class="table table-bordered table-sm">
|
<table class="table table-bordered table-sm">
|
||||||
<tr >
|
<tr>
|
||||||
<td style="border-top: none"></td>
|
<td style="border-top: none"></td>
|
||||||
<td class="text-right" style="border-top: none">{{ $t('per_serving') }}</td>
|
<td class="text-right" style="border-top: none">{{ $t('per_serving') }}</td>
|
||||||
<td class="text-right" style="border-top: none">{{ $t('total') }}</td>
|
<td class="text-right" style="border-top: none">{{ $t('total') }}</td>
|
||||||
@ -41,14 +41,18 @@
|
|||||||
|
|
||||||
<td class="align-middle text-center" v-if="!show_recipe_properties">
|
<td class="align-middle text-center" v-if="!show_recipe_properties">
|
||||||
<a href="#" @click="selected_property = p">
|
<a href="#" @click="selected_property = p">
|
||||||
<i v-if="p.missing_value" class="text-warning fas fa-exclamation-triangle"></i>
|
<!-- <i v-if="p.missing_value" class="text-warning fas fa-exclamation-triangle"></i>-->
|
||||||
<i v-if="!p.missing_value" class="text-muted fas fa-info-circle"></i>
|
<!-- <i v-if="!p.missing_value" class="text-muted fas fa-info-circle"></i>-->
|
||||||
|
<i class="text-muted fas fa-info-circle"></i>
|
||||||
|
<!-- TODO find solution for missing values as 0 can either be missing or actually correct for any given property -->
|
||||||
</a>
|
</a>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
|
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<b-button variant="success" :href="resolveDjangoUrl('view_property_editor', recipe.id)"><i class="fas fa-table"></i> {{ $t('Property_Editor') }}</b-button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
||||||
@ -79,7 +83,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import {ApiMixin, roundDecimals, StandardToasts} from "@/utils/utils";
|
import {ApiMixin, resolveDjangoUrl, roundDecimals, StandardToasts} from "@/utils/utils";
|
||||||
import GenericModalForm from "@/components/Modals/GenericModalForm.vue";
|
import GenericModalForm from "@/components/Modals/GenericModalForm.vue";
|
||||||
import {ApiApiFactory} from "@/utils/openapi/api";
|
import {ApiApiFactory} from "@/utils/openapi/api";
|
||||||
|
|
||||||
@ -153,11 +157,11 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function compare(a,b){
|
function compare(a, b) {
|
||||||
if(a.type.order > b.type.order){
|
if (a.type.order > b.type.order) {
|
||||||
return 1
|
return 1
|
||||||
}
|
}
|
||||||
if(a.type.order < b.type.order){
|
if (a.type.order < b.type.order) {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
@ -172,6 +176,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
resolveDjangoUrl,
|
||||||
roundDecimals,
|
roundDecimals,
|
||||||
openFoodEditModal: function (food) {
|
openFoodEditModal: function (food) {
|
||||||
console.log(food)
|
console.log(food)
|
||||||
|
@ -250,6 +250,10 @@ export default {
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.content:hover .card-img-overlay {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.content-details {
|
.content-details {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -10,6 +10,9 @@
|
|||||||
<a class="dropdown-item" :href="resolveDjangoUrl('edit_recipe', recipe.id)" v-if="!disabled_options.edit"><i
|
<a class="dropdown-item" :href="resolveDjangoUrl('edit_recipe', recipe.id)" v-if="!disabled_options.edit"><i
|
||||||
class="fas fa-pencil-alt fa-fw"></i> {{ $t("Edit") }}</a>
|
class="fas fa-pencil-alt fa-fw"></i> {{ $t("Edit") }}</a>
|
||||||
|
|
||||||
|
<a class="dropdown-item" :href="resolveDjangoUrl('view_property_editor', recipe.id)" v-if="!disabled_options.edit">
|
||||||
|
<i class="fas fa-table"></i> {{ $t("Property_Editor") }}</a>
|
||||||
|
|
||||||
<a class="dropdown-item" :href="resolveDjangoUrl('edit_convert_recipe', recipe.id)"
|
<a class="dropdown-item" :href="resolveDjangoUrl('edit_convert_recipe', recipe.id)"
|
||||||
v-if="!recipe.internal && !disabled_options.convert"><i class="fas fa-exchange-alt fa-fw"></i> {{ $t("convert_internal") }}</a>
|
v-if="!recipe.internal && !disabled_options.convert"><i class="fas fa-exchange-alt fa-fw"></i> {{ $t("convert_internal") }}</a>
|
||||||
|
|
||||||
@ -209,6 +212,7 @@ export default {
|
|||||||
this.entryEditing = this.options.entryEditing
|
this.entryEditing = this.options.entryEditing
|
||||||
this.entryEditing.recipe = this.recipe
|
this.entryEditing.recipe = this.recipe
|
||||||
this.entryEditing.from_date = moment(new Date()).format("YYYY-MM-DD")
|
this.entryEditing.from_date = moment(new Date()).format("YYYY-MM-DD")
|
||||||
|
this.entryEditing.to_date = moment(new Date()).format("YYYY-MM-DD")
|
||||||
this.$nextTick(function () {
|
this.$nextTick(function () {
|
||||||
this.$bvModal.show(`modal-meal-plan_${this.modal_id}`)
|
this.$bvModal.show(`modal-meal-plan_${this.modal_id}`)
|
||||||
})
|
})
|
||||||
@ -259,9 +263,11 @@ export default {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
if (recipe.nutrition !== null) {
|
|
||||||
delete recipe.nutrition.id
|
recipe.properties = recipe.properties.map(p => {
|
||||||
}
|
return { ...p, ...{ id: undefined, } }
|
||||||
|
})
|
||||||
|
|
||||||
apiClient
|
apiClient
|
||||||
.createRecipe(recipe)
|
.createRecipe(recipe)
|
||||||
.then((new_recipe) => {
|
.then((new_recipe) => {
|
||||||
|
@ -277,9 +277,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
handleResize: function () {
|
handleResize: function () {
|
||||||
if (document.getElementById('nutrition_container') !== null) {
|
if (document.getElementById('ingredient_container') !== null) {
|
||||||
this.ingredient_height = document.getElementById('ingredient_container').clientHeight - document.getElementById('nutrition_container').clientHeight
|
|
||||||
} else {
|
|
||||||
this.ingredient_height = document.getElementById('ingredient_container').clientHeight
|
this.ingredient_height = document.getElementById('ingredient_container').clientHeight
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"warning_feature_beta": "Denne funktion er i øjeblikket i BETA (test) stadie. Forvent fejl og fremtidige ændringer (hvor data kan mistes) ved brug af denne funktion.",
|
"warning_feature_beta": "Denne funktion er i øjeblikket i BETA (test)-stadie. Forvent fejl og fremtidige ændringer (hvor data kan mistes) ved brug af denne funktion.",
|
||||||
"err_fetching_resource": "Der opstod en fejl under indlæsning af denne ressource!",
|
"err_fetching_resource": "Der opstod en fejl under indlæsning af denne ressource!",
|
||||||
"err_creating_resource": "Der opstod en fejl under oprettelsen af denne ressource!",
|
"err_creating_resource": "Der opstod en fejl under oprettelsen af denne ressource!",
|
||||||
"err_updating_resource": "Der opstod en fejl under opdateringen af denne ressource!",
|
"err_updating_resource": "Der opstod en fejl under opdateringen af denne ressource!",
|
||||||
@ -477,5 +477,62 @@
|
|||||||
"Unpin": "Frigør",
|
"Unpin": "Frigør",
|
||||||
"PinnedConfirmation": "{recipe} er fastgjort.",
|
"PinnedConfirmation": "{recipe} er fastgjort.",
|
||||||
"UnpinnedConfirmation": "{recipe} er frigjort.",
|
"UnpinnedConfirmation": "{recipe} er frigjort.",
|
||||||
"Combine_All_Steps": "Kombiner alle trin til ét felt."
|
"Combine_All_Steps": "Kombiner alle trin til ét felt.",
|
||||||
|
"converted_unit": "Konverteret enhed",
|
||||||
|
"Property": "Egenskab",
|
||||||
|
"OrderInformation": "Objekter er rangeret fra små til store tal.",
|
||||||
|
"show_ingredients_table": "Vis ingredienser i en tabel ved siden af trinnets tekst",
|
||||||
|
"tsp": "teaspoon [tsp] (US, volumen)",
|
||||||
|
"imperial_fluid_ounce": "imperial fluid ounce [imp fl oz] (UK, volumen)",
|
||||||
|
"imperial_tsp": "imperial teaspoon [imp tsp] (UK, volumen)",
|
||||||
|
"open_data_help_text": "Tandoor Open Data projektet tilføjer netværksgenereret data til Tandoor. Dette felt bliver udfyldt automatisk under importering og muliggør fremtidige opdateringer.",
|
||||||
|
"converted_amount": "Konverteret mængde",
|
||||||
|
"StartDate": "Startdato",
|
||||||
|
"EndDate": "Slutdato",
|
||||||
|
"show_step_ingredients_setting": "Vis ingredienser ved siden af opskrifttrin",
|
||||||
|
"l": "liter [l] (metrisk, volumen)",
|
||||||
|
"g": "gram [g] (metrisk, vægt)",
|
||||||
|
"kg": "kilogram [kg] (metrisk, vægt)",
|
||||||
|
"ounce": "ounce [oz] (vægt)",
|
||||||
|
"pound": "pund (vægt)",
|
||||||
|
"ml": "milliliter [ml] (metrisk, volumen)",
|
||||||
|
"fluid_ounce": "flydende ounce [fl oz] (US, volumen)",
|
||||||
|
"pint": "pint [pt] (US, volumen)",
|
||||||
|
"Back": "Tilbage",
|
||||||
|
"quart": "quart [qt] (US, volumen)",
|
||||||
|
"recipe_property_info": "Du kan også tilføje næringsindhold til ingredienser for at udregne indholdet automatisk baseret på din opskrift!",
|
||||||
|
"per_serving": "per serveringer",
|
||||||
|
"Open_Data_Slug": "Open Data Slug",
|
||||||
|
"Open_Data_Import": "Open Data importering",
|
||||||
|
"Data_Import_Info": "Udbyg dit Space og gør din opskriftsamling bedre ved at importere en netværkskurateret liste af ingredienser, enheder og mere.",
|
||||||
|
"Update_Existing_Data": "Opdaterer eksisterende data",
|
||||||
|
"make_now_count": "Oftest manglende ingredienser",
|
||||||
|
"Welcome": "Velkommen",
|
||||||
|
"imperial_pint": "imperial pint [imp pt] (UK, volumen)",
|
||||||
|
"Alignment": "Justering",
|
||||||
|
"gallon": "gallon [gal] (US, volumen)",
|
||||||
|
"Never_Unit": "Aldrig enhed",
|
||||||
|
"FDC_ID": "FDC ID",
|
||||||
|
"FDC_ID_help": "FDC database ID",
|
||||||
|
"Use_Metric": "Benyt metriske enheder",
|
||||||
|
"Learn_More": "Lær mere",
|
||||||
|
"base_unit": "Basisenhed",
|
||||||
|
"base_amount": "Basismængde",
|
||||||
|
"Datatype": "Datatype",
|
||||||
|
"Number of Objects": "Antal objekter",
|
||||||
|
"Conversion": "Konversion",
|
||||||
|
"Properties": "Egenskaber",
|
||||||
|
"show_step_ingredients_setting_help": "Tilføj ingredienstabel ved siden af opskrifttrin. Tilføjes ved oprettelsen. Kan overskrives under rediger opskrift.",
|
||||||
|
"show_step_ingredients": "Vis trinnets ingredienser",
|
||||||
|
"hide_step_ingredients": "Skjul trinnets ingredienser",
|
||||||
|
"total": "total",
|
||||||
|
"tbsp": "tablespoon [tbsp] (US, volumen)",
|
||||||
|
"imperial_quart": "imperial quart [imp qt] (UK, volumen)",
|
||||||
|
"imperial_gallon": "imperial gal [imp gal] (UK, volumen)",
|
||||||
|
"imperial_tbsp": "imperial tablespoon [imp tbsp] (UK, volumen)",
|
||||||
|
"Choose_Category": "Vælg kategori",
|
||||||
|
"Transpose_Words": "Omstil ord",
|
||||||
|
"Name_Replace": "Erstat navn",
|
||||||
|
"Food_Replace": "Erstat ingrediens",
|
||||||
|
"Unit_Replace": "Erstat enhed"
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
"err_deleting_protected_resource": "The object you are trying to delete is still used and can't be deleted.",
|
"err_deleting_protected_resource": "The object you are trying to delete is still used and can't be deleted.",
|
||||||
"err_moving_resource": "There was an error moving a resource!",
|
"err_moving_resource": "There was an error moving a resource!",
|
||||||
"err_merging_resource": "There was an error merging a resource!",
|
"err_merging_resource": "There was an error merging a resource!",
|
||||||
|
"err_importing_recipe": "There was an error importing the recipe!",
|
||||||
"success_fetching_resource": "Successfully fetched a resource!",
|
"success_fetching_resource": "Successfully fetched a resource!",
|
||||||
"success_creating_resource": "Successfully created a resource!",
|
"success_creating_resource": "Successfully created a resource!",
|
||||||
"success_updating_resource": "Successfully updated a resource!",
|
"success_updating_resource": "Successfully updated a resource!",
|
||||||
@ -80,8 +81,12 @@
|
|||||||
"open_data_help_text": "The Tandoor Open Data project provides community contributed data for Tandoor. This field is filled automatically when importing it and allows updates in the future.",
|
"open_data_help_text": "The Tandoor Open Data project provides community contributed data for Tandoor. This field is filled automatically when importing it and allows updates in the future.",
|
||||||
"Open_Data_Slug": "Open Data Slug",
|
"Open_Data_Slug": "Open Data Slug",
|
||||||
"Open_Data_Import": "Open Data Import",
|
"Open_Data_Import": "Open Data Import",
|
||||||
|
"Properties_Food_Amount": "Properties Food Amount",
|
||||||
|
"Properties_Food_Unit": "Properties Food Unit",
|
||||||
"FDC_ID": "FDC ID",
|
"FDC_ID": "FDC ID",
|
||||||
|
"FDC_Search": "FDC Search",
|
||||||
"FDC_ID_help": "FDC database ID",
|
"FDC_ID_help": "FDC database ID",
|
||||||
|
"property_type_fdc_hint": "Only property types with an FDC ID can automatically pull data from the FDC database",
|
||||||
"Data_Import_Info": "Enhance your Space by importing a community curated list of foods, units and more to improve your recipe collection.",
|
"Data_Import_Info": "Enhance your Space by importing a community curated list of foods, units and more to improve your recipe collection.",
|
||||||
"Update_Existing_Data": "Update Existing Data",
|
"Update_Existing_Data": "Update Existing Data",
|
||||||
"Use_Metric": "Use Metric Units",
|
"Use_Metric": "Use Metric Units",
|
||||||
@ -182,6 +187,7 @@
|
|||||||
"move_title": "Move {type}",
|
"move_title": "Move {type}",
|
||||||
"Food": "Food",
|
"Food": "Food",
|
||||||
"Property": "Property",
|
"Property": "Property",
|
||||||
|
"Property_Editor": "Property Editor",
|
||||||
"Conversion": "Conversion",
|
"Conversion": "Conversion",
|
||||||
"Original_Text": "Original Text",
|
"Original_Text": "Original Text",
|
||||||
"Recipe_Book": "Recipe Book",
|
"Recipe_Book": "Recipe Book",
|
||||||
|
@ -162,7 +162,7 @@
|
|||||||
"Unit_Alias": "Alias Unità",
|
"Unit_Alias": "Alias Unità",
|
||||||
"Keyword_Alias": "Alias Parola Chiave",
|
"Keyword_Alias": "Alias Parola Chiave",
|
||||||
"Table_of_Contents": "Indice dei contenuti",
|
"Table_of_Contents": "Indice dei contenuti",
|
||||||
"warning_feature_beta": "Questa funzione è attualmente in BETA (non è completa). Potrebbero verificarsi delle anomalie e modifiche che in futuro potrebbero bloccare la funzionalità stessa o rimuove i dati correlati a essa.",
|
"warning_feature_beta": "Questa funzione è attualmente in BETA (non è completa). Potrebbero verificarsi delle anomalie e modifiche che in futuro potrebbero bloccare la funzionalità stessa o rimuove i dati correlati ad essa.",
|
||||||
"Shopping_list": "Lista della spesa",
|
"Shopping_list": "Lista della spesa",
|
||||||
"Title": "Titolo",
|
"Title": "Titolo",
|
||||||
"Create_New_Meal_Type": "Aggiungi nuovo tipo di pasto",
|
"Create_New_Meal_Type": "Aggiungi nuovo tipo di pasto",
|
||||||
@ -393,7 +393,7 @@
|
|||||||
"view_recipe": "Mostra ricetta",
|
"view_recipe": "Mostra ricetta",
|
||||||
"copy_to_new": "Copia in una nuova ricetta",
|
"copy_to_new": "Copia in una nuova ricetta",
|
||||||
"Pinned": "Fissato",
|
"Pinned": "Fissato",
|
||||||
"App": "App",
|
"App": "Applicazione",
|
||||||
"filter": "Filtro",
|
"filter": "Filtro",
|
||||||
"explain": "Maggior informazioni",
|
"explain": "Maggior informazioni",
|
||||||
"Website": "Sito web",
|
"Website": "Sito web",
|
||||||
|
@ -23,7 +23,7 @@ export class Models {
|
|||||||
false: undefined,
|
false: undefined,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
tree: { default: undefined },
|
tree: {default: undefined},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
@ -50,7 +50,7 @@ export class Models {
|
|||||||
type: "lookup",
|
type: "lookup",
|
||||||
field: "target",
|
field: "target",
|
||||||
list: "self",
|
list: "self",
|
||||||
sticky_options: [{ id: 0, name: "tree_root" }],
|
sticky_options: [{id: 0, name: "tree_root"}],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -71,7 +71,7 @@ export class Models {
|
|||||||
food_onhand: true,
|
food_onhand: true,
|
||||||
shopping: true,
|
shopping: true,
|
||||||
},
|
},
|
||||||
tags: [{ field: "supermarket_category", label: "name", color: "info" }],
|
tags: [{field: "supermarket_category", label: "name", color: "info"}],
|
||||||
// REQUIRED: unordered array of fields that can be set during create
|
// REQUIRED: unordered array of fields that can be set during create
|
||||||
create: {
|
create: {
|
||||||
// if not defined partialUpdate will use the same parameters, prepending 'id'
|
// if not defined partialUpdate will use the same parameters, prepending 'id'
|
||||||
@ -177,7 +177,7 @@ export class Models {
|
|||||||
field: "substitute_siblings",
|
field: "substitute_siblings",
|
||||||
label: "substitute_siblings", // form.label always translated in utils.getForm()
|
label: "substitute_siblings", // form.label always translated in utils.getForm()
|
||||||
help_text: "substitute_siblings_help", // form.help_text always translated
|
help_text: "substitute_siblings_help", // form.help_text always translated
|
||||||
condition: { field: "parent", value: true, condition: "field_exists" },
|
condition: {field: "parent", value: true, condition: "field_exists"},
|
||||||
},
|
},
|
||||||
substitute_children: {
|
substitute_children: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
@ -186,7 +186,7 @@ export class Models {
|
|||||||
field: "substitute_children",
|
field: "substitute_children",
|
||||||
label: "substitute_children",
|
label: "substitute_children",
|
||||||
help_text: "substitute_children_help",
|
help_text: "substitute_children_help",
|
||||||
condition: { field: "numchild", value: 0, condition: "gt" },
|
condition: {field: "numchild", value: 0, condition: "gt"},
|
||||||
},
|
},
|
||||||
inherit_fields: {
|
inherit_fields: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
@ -196,7 +196,7 @@ export class Models {
|
|||||||
field: "inherit_fields",
|
field: "inherit_fields",
|
||||||
list: "FOOD_INHERIT_FIELDS",
|
list: "FOOD_INHERIT_FIELDS",
|
||||||
label: "InheritFields",
|
label: "InheritFields",
|
||||||
condition: { field: "food_children_exist", value: true, condition: "preference_equals" },
|
condition: {field: "food_children_exist", value: true, condition: "preference_equals"},
|
||||||
help_text: "InheritFields_help",
|
help_text: "InheritFields_help",
|
||||||
},
|
},
|
||||||
child_inherit_fields: {
|
child_inherit_fields: {
|
||||||
@ -207,7 +207,7 @@ export class Models {
|
|||||||
field: "child_inherit_fields",
|
field: "child_inherit_fields",
|
||||||
list: "FOOD_INHERIT_FIELDS",
|
list: "FOOD_INHERIT_FIELDS",
|
||||||
label: "ChildInheritFields", // form.label always translated in utils.getForm()
|
label: "ChildInheritFields", // form.label always translated in utils.getForm()
|
||||||
condition: { field: "numchild", value: 0, condition: "gt" },
|
condition: {field: "numchild", value: 0, condition: "gt"},
|
||||||
help_text: "ChildInheritFields_help", // form.help_text always translated
|
help_text: "ChildInheritFields_help", // form.help_text always translated
|
||||||
},
|
},
|
||||||
reset_inherit: {
|
reset_inherit: {
|
||||||
@ -217,7 +217,7 @@ export class Models {
|
|||||||
field: "reset_inherit",
|
field: "reset_inherit",
|
||||||
label: "reset_children",
|
label: "reset_children",
|
||||||
help_text: "reset_children_help",
|
help_text: "reset_children_help",
|
||||||
condition: { field: "numchild", value: 0, condition: "gt" },
|
condition: {field: "numchild", value: 0, condition: "gt"},
|
||||||
},
|
},
|
||||||
form_function: "FoodCreateDefault",
|
form_function: "FoodCreateDefault",
|
||||||
},
|
},
|
||||||
@ -304,24 +304,24 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "choice",
|
type: "choice",
|
||||||
options: [
|
options: [
|
||||||
{ value: "g", text: "g" },
|
{value: "g", text: "g"},
|
||||||
{ value: "kg", text: "kg" },
|
{value: "kg", text: "kg"},
|
||||||
{ value: "ounce", text: "ounce" },
|
{value: "ounce", text: "ounce"},
|
||||||
{ value: "pound", text: "pound" },
|
{value: "pound", text: "pound"},
|
||||||
{ value: "ml", text: "ml" },
|
{value: "ml", text: "ml"},
|
||||||
{ value: "l", text: "l" },
|
{value: "l", text: "l"},
|
||||||
{ value: "fluid_ounce", text: "fluid_ounce" },
|
{value: "fluid_ounce", text: "fluid_ounce"},
|
||||||
{ value: "pint", text: "pint" },
|
{value: "pint", text: "pint"},
|
||||||
{ value: "quart", text: "quart" },
|
{value: "quart", text: "quart"},
|
||||||
{ value: "gallon", text: "gallon" },
|
{value: "gallon", text: "gallon"},
|
||||||
{ value: "tbsp", text: "tbsp" },
|
{value: "tbsp", text: "tbsp"},
|
||||||
{ value: "tsp", text: "tsp" },
|
{value: "tsp", text: "tsp"},
|
||||||
{ value: "imperial_fluid_ounce", text: "imperial_fluid_ounce" },
|
{value: "imperial_fluid_ounce", text: "imperial_fluid_ounce"},
|
||||||
{ value: "imperial_pint", text: "imperial_pint" },
|
{value: "imperial_pint", text: "imperial_pint"},
|
||||||
{ value: "imperial_quart", text: "imperial_quart" },
|
{value: "imperial_quart", text: "imperial_quart"},
|
||||||
{ value: "imperial_gallon", text: "imperial_gallon" },
|
{value: "imperial_gallon", text: "imperial_gallon"},
|
||||||
{ value: "imperial_tbsp", text: "imperial_tbsp" },
|
{value: "imperial_tbsp", text: "imperial_tbsp"},
|
||||||
{ value: "imperial_tsp", text: "imperial_tsp" },
|
{value: "imperial_tsp", text: "imperial_tsp"},
|
||||||
],
|
],
|
||||||
field: "base_unit",
|
field: "base_unit",
|
||||||
label: "Base Unit",
|
label: "Base Unit",
|
||||||
@ -457,7 +457,7 @@ export class Models {
|
|||||||
static SUPERMARKET = {
|
static SUPERMARKET = {
|
||||||
name: "Supermarket",
|
name: "Supermarket",
|
||||||
apiName: "Supermarket",
|
apiName: "Supermarket",
|
||||||
ordered_tags: [{ field: "category_to_supermarket", label: "category::name", color: "info" }],
|
ordered_tags: [{field: "category_to_supermarket", label: "category::name", color: "info"}],
|
||||||
create: {
|
create: {
|
||||||
params: [["name", "description", "category_to_supermarket"]],
|
params: [["name", "description", "category_to_supermarket"]],
|
||||||
form: {
|
form: {
|
||||||
@ -540,16 +540,16 @@ export class Models {
|
|||||||
form_field: true,
|
form_field: true,
|
||||||
type: "choice",
|
type: "choice",
|
||||||
options: [
|
options: [
|
||||||
{ value: "FOOD_ALIAS", text: "Food_Alias" },
|
{value: "FOOD_ALIAS", text: "Food_Alias"},
|
||||||
{ value: "UNIT_ALIAS", text: "Unit_Alias" },
|
{value: "UNIT_ALIAS", text: "Unit_Alias"},
|
||||||
{ value: "KEYWORD_ALIAS", text: "Keyword_Alias" },
|
{value: "KEYWORD_ALIAS", text: "Keyword_Alias"},
|
||||||
{ value: "NAME_REPLACE", text: "Name_Replace" },
|
{value: "NAME_REPLACE", text: "Name_Replace"},
|
||||||
{ value: "DESCRIPTION_REPLACE", text: "Description_Replace" },
|
{value: "DESCRIPTION_REPLACE", text: "Description_Replace"},
|
||||||
{ value: "INSTRUCTION_REPLACE", text: "Instruction_Replace" },
|
{value: "INSTRUCTION_REPLACE", text: "Instruction_Replace"},
|
||||||
{ value: "FOOD_REPLACE", text: "Food_Replace" },
|
{value: "FOOD_REPLACE", text: "Food_Replace"},
|
||||||
{ value: "UNIT_REPLACE", text: "Unit_Replace" },
|
{value: "UNIT_REPLACE", text: "Unit_Replace"},
|
||||||
{ value: "NEVER_UNIT", text: "Never_Unit" },
|
{value: "NEVER_UNIT", text: "Never_Unit"},
|
||||||
{ value: "TRANSPOSE_WORDS", text: "Transpose_Words" },
|
{value: "TRANSPOSE_WORDS", text: "Transpose_Words"},
|
||||||
],
|
],
|
||||||
field: "type",
|
field: "type",
|
||||||
label: "Type",
|
label: "Type",
|
||||||
@ -700,7 +700,7 @@ export class Models {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
create: {
|
create: {
|
||||||
params: [["name", "unit", "description", "order"]],
|
params: [["name", "unit", "description", "order", "fdc_id"]],
|
||||||
form: {
|
form: {
|
||||||
show_help: true,
|
show_help: true,
|
||||||
name: {
|
name: {
|
||||||
@ -733,12 +733,241 @@ export class Models {
|
|||||||
field: "order",
|
field: "order",
|
||||||
label: "Order",
|
label: "Order",
|
||||||
placeholder: "",
|
placeholder: "",
|
||||||
optional: false,
|
optional: true,
|
||||||
help_text: "OrderInformation",
|
help_text: "OrderInformation",
|
||||||
},
|
},
|
||||||
fdc_id: {
|
fdc_id: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "text",
|
type: "choice",
|
||||||
|
options: [
|
||||||
|
{value: 1002, text: "Nitrogen [g] (1002)"},
|
||||||
|
{value: 1003, text: "Protein [g] (1003)"},
|
||||||
|
{value: 1004, text: "Total lipid (fat) [g] (1004)"},
|
||||||
|
{value: 1005, text: "Carbohydrate, by difference [g] (1005)"},
|
||||||
|
{value: 1007, text: "Ash [g] (1007)"},
|
||||||
|
{value: 1008, text: "Energy [kcal] (1008)"},
|
||||||
|
{value: 1009, text: "Starch [g] (1009)"},
|
||||||
|
{value: 1010, text: "Sucrose [g] (1010)"},
|
||||||
|
{value: 1011, text: "Glucose [g] (1011)"},
|
||||||
|
{value: 1012, text: "Fructose [g] (1012)"},
|
||||||
|
{value: 1013, text: "Lactose [g] (1013)"},
|
||||||
|
{value: 1014, text: "Maltose [g] (1014)"},
|
||||||
|
{value: 1024, text: "Specific Gravity [sp gr] (1024)"},
|
||||||
|
{value: 1032, text: "Citric acid [mg] (1032)"},
|
||||||
|
{value: 1039, text: "Malic acid [mg] (1039)"},
|
||||||
|
{value: 1041, text: "Oxalic acid [mg] (1041)"},
|
||||||
|
{value: 1043, text: "Pyruvic acid [mg] (1043)"},
|
||||||
|
{value: 1044, text: "Quinic acid [mg] (1044)"},
|
||||||
|
{value: 1050, text: "Carbohydrate, by summation [g] (1050)"},
|
||||||
|
{value: 1051, text: "Water [g] (1051)"},
|
||||||
|
{value: 1062, text: "Energy [kJ] (1062)"},
|
||||||
|
{value: 1063, text: "Sugars, Total [g] (1063)"},
|
||||||
|
{value: 1075, text: "Galactose [g] (1075)"},
|
||||||
|
{value: 1076, text: "Raffinose [g] (1076)"},
|
||||||
|
{value: 1077, text: "Stachyose [g] (1077)"},
|
||||||
|
{value: 1079, text: "Fiber, total dietary [g] (1079)"},
|
||||||
|
{value: 1082, text: "Fiber, soluble [g] (1082)"},
|
||||||
|
{value: 1084, text: "Fiber, insoluble [g] (1084)"},
|
||||||
|
{value: 1085, text: "Total fat (NLEA) [g] (1085)"},
|
||||||
|
{value: 1087, text: "Calcium, Ca [mg] (1087)"},
|
||||||
|
{value: 1089, text: "Iron, Fe [mg] (1089)"},
|
||||||
|
{value: 1090, text: "Magnesium, Mg [mg] (1090)"},
|
||||||
|
{value: 1091, text: "Phosphorus, P [mg] (1091)"},
|
||||||
|
{value: 1092, text: "Potassium, K [mg] (1092)"},
|
||||||
|
{value: 1093, text: "Sodium, Na [mg] (1093)"},
|
||||||
|
{value: 1094, text: "Sulfur, S [mg] (1094)"},
|
||||||
|
{value: 1095, text: "Zinc, Zn [mg] (1095)"},
|
||||||
|
{value: 1097, text: "Cobalt, Co [µg] (1097)"},
|
||||||
|
{value: 1098, text: "Copper, Cu [mg] (1098)"},
|
||||||
|
{value: 1100, text: "Iodine, I [µg] (1100)"},
|
||||||
|
{value: 1101, text: "Manganese, Mn [mg] (1101)"},
|
||||||
|
{value: 1102, text: "Molybdenum, Mo [µg] (1102)"},
|
||||||
|
{value: 1103, text: "Selenium, Se [µg] (1103)"},
|
||||||
|
{value: 1105, text: "Retinol [µg] (1105)"},
|
||||||
|
{value: 1106, text: "Vitamin A, RAE [µg] (1106)"},
|
||||||
|
{value: 1107, text: "Carotene, beta [µg] (1107)"},
|
||||||
|
{value: 1108, text: "Carotene, alpha [µg] (1108)"},
|
||||||
|
{value: 1109, text: "Vitamin E (alpha-tocopherol) [mg] (1109)"},
|
||||||
|
{value: 1110, text: "Vitamin D (D2 + D3), International Units [IU] (1110)"},
|
||||||
|
{value: 1111, text: "Vitamin D2 (ergocalciferol) [µg] (1111)"},
|
||||||
|
{value: 1112, text: "Vitamin D3 (cholecalciferol) [µg] (1112)"},
|
||||||
|
{value: 1113, text: "25-hydroxycholecalciferol [µg] (1113)"},
|
||||||
|
{value: 1114, text: "Vitamin D (D2 + D3) [µg] (1114)"},
|
||||||
|
{value: 1116, text: "Phytoene [µg] (1116)"},
|
||||||
|
{value: 1117, text: "Phytofluene [µg] (1117)"},
|
||||||
|
{value: 1118, text: "Carotene, gamma [µg] (1118)"},
|
||||||
|
{value: 1119, text: "Zeaxanthin [µg] (1119)"},
|
||||||
|
{value: 1120, text: "Cryptoxanthin, beta [µg] (1120)"},
|
||||||
|
{value: 1121, text: "Lutein [µg] (1121)"},
|
||||||
|
{value: 1122, text: "Lycopene [µg] (1122)"},
|
||||||
|
{value: 1123, text: "Lutein + zeaxanthin [µg] (1123)"},
|
||||||
|
{value: 1125, text: "Tocopherol, beta [mg] (1125)"},
|
||||||
|
{value: 1126, text: "Tocopherol, gamma [mg] (1126)"},
|
||||||
|
{value: 1127, text: "Tocopherol, delta [mg] (1127)"},
|
||||||
|
{value: 1128, text: "Tocotrienol, alpha [mg] (1128)"},
|
||||||
|
{value: 1129, text: "Tocotrienol, beta [mg] (1129)"},
|
||||||
|
{value: 1130, text: "Tocotrienol, gamma [mg] (1130)"},
|
||||||
|
{value: 1131, text: "Tocotrienol, delta [mg] (1131)"},
|
||||||
|
{value: 1137, text: "Boron, B [µg] (1137)"},
|
||||||
|
{value: 1146, text: "Nickel, Ni [µg] (1146)"},
|
||||||
|
{value: 1159, text: "cis-beta-Carotene [µg] (1159)"},
|
||||||
|
{value: 1160, text: "cis-Lycopene [µg] (1160)"},
|
||||||
|
{value: 1161, text: "cis-Lutein/Zeaxanthin [µg] (1161)"},
|
||||||
|
{value: 1162, text: "Vitamin C, total ascorbic acid [mg] (1162)"},
|
||||||
|
{value: 1165, text: "Thiamin [mg] (1165)"},
|
||||||
|
{value: 1166, text: "Riboflavin [mg] (1166)"},
|
||||||
|
{value: 1167, text: "Niacin [mg] (1167)"},
|
||||||
|
{value: 1170, text: "Pantothenic acid [mg] (1170)"},
|
||||||
|
{value: 1175, text: "Vitamin B-6 [mg] (1175)"},
|
||||||
|
{value: 1176, text: "Biotin [µg] (1176)"},
|
||||||
|
{value: 1177, text: "Folate, total [µg] (1177)"},
|
||||||
|
{value: 1178, text: "Vitamin B-12 [µg] (1178)"},
|
||||||
|
{value: 1180, text: "Choline, total [mg] (1180)"},
|
||||||
|
{value: 1183, text: "Vitamin K (Menaquinone-4) [µg] (1183)"},
|
||||||
|
{value: 1184, text: "Vitamin K (Dihydrophylloquinone) [µg] (1184)"},
|
||||||
|
{value: 1185, text: "Vitamin K (phylloquinone) [µg] (1185)"},
|
||||||
|
{value: 1188, text: "5-methyl tetrahydrofolate (5-MTHF) [µg] (1188)"},
|
||||||
|
{value: 1191, text: "10-Formyl folic acid (10HCOFA) [µg] (1191)"},
|
||||||
|
{value: 1192, text: "5-Formyltetrahydrofolic acid (5-HCOH4 [µg] (1192)"},
|
||||||
|
{value: 1194, text: "Choline, free [mg] (1194)"},
|
||||||
|
{value: 1195, text: "Choline, from phosphocholine [mg] (1195)"},
|
||||||
|
{value: 1196, text: "Choline, from phosphotidyl choline [mg] (1196)"},
|
||||||
|
{value: 1197, text: "Choline, from glycerophosphocholine [mg] (1197)"},
|
||||||
|
{value: 1198, text: "Betaine [mg] (1198)"},
|
||||||
|
{value: 1199, text: "Choline, from sphingomyelin [mg] (1199)"},
|
||||||
|
{value: 1210, text: "Tryptophan [g] (1210)"},
|
||||||
|
{value: 1211, text: "Threonine [g] (1211)"},
|
||||||
|
{value: 1212, text: "Isoleucine [g] (1212)"},
|
||||||
|
{value: 1213, text: "Leucine [g] (1213)"},
|
||||||
|
{value: 1214, text: "Lysine [g] (1214)"},
|
||||||
|
{value: 1215, text: "Methionine [g] (1215)"},
|
||||||
|
{value: 1216, text: "Cystine [g] (1216)"},
|
||||||
|
{value: 1217, text: "Phenylalanine [g] (1217)"},
|
||||||
|
{value: 1218, text: "Tyrosine [g] (1218)"},
|
||||||
|
{value: 1219, text: "Valine [g] (1219)"},
|
||||||
|
{value: 1220, text: "Arginine [g] (1220)"},
|
||||||
|
{value: 1221, text: "Histidine [g] (1221)"},
|
||||||
|
{value: 1222, text: "Alanine [g] (1222)"},
|
||||||
|
{value: 1223, text: "Aspartic acid [g] (1223)"},
|
||||||
|
{value: 1224, text: "Glutamic acid [g] (1224)"},
|
||||||
|
{value: 1225, text: "Glycine [g] (1225)"},
|
||||||
|
{value: 1226, text: "Proline [g] (1226)"},
|
||||||
|
{value: 1227, text: "Serine [g] (1227)"},
|
||||||
|
{value: 1228, text: "Hydroxyproline [g] (1228)"},
|
||||||
|
{value: 1232, text: "Cysteine [g] (1232)"},
|
||||||
|
{value: 1253, text: "Cholesterol [mg] (1253)"},
|
||||||
|
{value: 1257, text: "Fatty acids, total trans [g] (1257)"},
|
||||||
|
{value: 1258, text: "Fatty acids, total saturated [g] (1258)"},
|
||||||
|
{value: 1259, text: "SFA 4:0 [g] (1259)"},
|
||||||
|
{value: 1260, text: "SFA 6:0 [g] (1260)"},
|
||||||
|
{value: 1261, text: "SFA 8:0 [g] (1261)"},
|
||||||
|
{value: 1262, text: "SFA 10:0 [g] (1262)"},
|
||||||
|
{value: 1263, text: "SFA 12:0 [g] (1263)"},
|
||||||
|
{value: 1264, text: "SFA 14:0 [g] (1264)"},
|
||||||
|
{value: 1265, text: "SFA 16:0 [g] (1265)"},
|
||||||
|
{value: 1266, text: "SFA 18:0 [g] (1266)"},
|
||||||
|
{value: 1267, text: "SFA 20:0 [g] (1267)"},
|
||||||
|
{value: 1268, text: "MUFA 18:1 [g] (1268)"},
|
||||||
|
{value: 1269, text: "PUFA 18:2 [g] (1269)"},
|
||||||
|
{value: 1270, text: "PUFA 18:3 [g] (1270)"},
|
||||||
|
{value: 1271, text: "PUFA 20:4 [g] (1271)"},
|
||||||
|
{value: 1272, text: "PUFA 22:6 n-3 (DHA) [g] (1272)"},
|
||||||
|
{value: 1273, text: "SFA 22:0 [g] (1273)"},
|
||||||
|
{value: 1276, text: "PUFA 18:4 [g] (1276)"},
|
||||||
|
{value: 1277, text: "MUFA 20:1 [g] (1277)"},
|
||||||
|
{value: 1278, text: "PUFA 20:5 n-3 (EPA) [g] (1278)"},
|
||||||
|
{value: 1279, text: "MUFA 22:1 [g] (1279)"},
|
||||||
|
{value: 1280, text: "PUFA 22:5 n-3 (DPA) [g] (1280)"},
|
||||||
|
{value: 1281, text: "TFA 14:1 t [g] (1281)"},
|
||||||
|
{value: 1284, text: "Ergosterol [mg] (1284)"},
|
||||||
|
{value: 1285, text: "Stigmasterol [mg] (1285)"},
|
||||||
|
{value: 1286, text: "Campesterol [mg] (1286)"},
|
||||||
|
{value: 1287, text: "Brassicasterol [mg] (1287)"},
|
||||||
|
{value: 1288, text: "Beta-sitosterol [mg] (1288)"},
|
||||||
|
{value: 1289, text: "Campestanol [mg] (1289)"},
|
||||||
|
{value: 1292, text: "Fatty acids, total monounsaturated [g] (1292)"},
|
||||||
|
{value: 1293, text: "Fatty acids, total polyunsaturated [g] (1293)"},
|
||||||
|
{value: 1294, text: "Beta-sitostanol [mg] (1294)"},
|
||||||
|
{value: 1296, text: "Delta-5-avenasterol [mg] (1296)"},
|
||||||
|
{value: 1298, text: "Phytosterols, other [mg] (1298)"},
|
||||||
|
{value: 1299, text: "SFA 15:0 [g] (1299)"},
|
||||||
|
{value: 1300, text: "SFA 17:0 [g] (1300)"},
|
||||||
|
{value: 1301, text: "SFA 24:0 [g] (1301)"},
|
||||||
|
{value: 1303, text: "TFA 16:1 t [g] (1303)"},
|
||||||
|
{value: 1304, text: "TFA 18:1 t [g] (1304)"},
|
||||||
|
{value: 1305, text: "TFA 22:1 t [g] (1305)"},
|
||||||
|
{value: 1306, text: "TFA 18:2 t not further defined [g] (1306)"},
|
||||||
|
{value: 1311, text: "PUFA 18:2 CLAs [g] (1311)"},
|
||||||
|
{value: 1312, text: "MUFA 24:1 c [g] (1312)"},
|
||||||
|
{value: 1313, text: "PUFA 20:2 n-6 c,c [g] (1313)"},
|
||||||
|
{value: 1314, text: "MUFA 16:1 c [g] (1314)"},
|
||||||
|
{value: 1315, text: "MUFA 18:1 c [g] (1315)"},
|
||||||
|
{value: 1316, text: "PUFA 18:2 n-6 c,c [g] (1316)"},
|
||||||
|
{value: 1317, text: "MUFA 22:1 c [g] (1317)"},
|
||||||
|
{value: 1321, text: "PUFA 18:3 n-6 c,c,c [g] (1321)"},
|
||||||
|
{value: 1323, text: "MUFA 17:1 [g] (1323)"},
|
||||||
|
{value: 1325, text: "PUFA 20:3 [g] (1325)"},
|
||||||
|
{value: 1329, text: "Fatty acids, total trans-monoenoic [g] (1329)"},
|
||||||
|
{value: 1330, text: "Fatty acids, total trans-dienoic [g] (1330)"},
|
||||||
|
{value: 1331, text: "Fatty acids, total trans-polyenoic [g] (1331)"},
|
||||||
|
{value: 1333, text: "MUFA 15:1 [g] (1333)"},
|
||||||
|
{value: 1334, text: "PUFA 22:2 [g] (1334)"},
|
||||||
|
{value: 1335, text: "SFA 11:0 [g] (1335)"},
|
||||||
|
{value: 1340, text: "Daidzein [mg] (1340)"},
|
||||||
|
{value: 1341, text: "Genistein [mg] (1341)"},
|
||||||
|
{value: 1404, text: "PUFA 18:3 n-3 c,c,c (ALA) [g] (1404)"},
|
||||||
|
{value: 1405, text: "PUFA 20:3 n-3 [g] (1405)"},
|
||||||
|
{value: 1406, text: "PUFA 20:3 n-6 [g] (1406)"},
|
||||||
|
{value: 1409, text: "PUFA 18:3i [g] (1409)"},
|
||||||
|
{value: 1411, text: "PUFA 22:4 [g] (1411)"},
|
||||||
|
{value: 1414, text: "PUFA 20:3 n-9 [g] (1414)"},
|
||||||
|
{value: 2000, text: "Sugars, total including NLEA [g] (2000)"},
|
||||||
|
{value: 2003, text: "SFA 5:0 [g] (2003)"},
|
||||||
|
{value: 2004, text: "SFA 7:0 [g] (2004)"},
|
||||||
|
{value: 2005, text: "SFA 9:0 [g] (2005)"},
|
||||||
|
{value: 2006, text: "SFA 21:0 [g] (2006)"},
|
||||||
|
{value: 2007, text: "SFA 23:0 [g] (2007)"},
|
||||||
|
{value: 2008, text: "MUFA 12:1 [g] (2008)"},
|
||||||
|
{value: 2009, text: "MUFA 14:1 c [g] (2009)"},
|
||||||
|
{value: 2010, text: "MUFA 17:1 c [g] (2010)"},
|
||||||
|
{value: 2012, text: "MUFA 20:1 c [g] (2012)"},
|
||||||
|
{value: 2013, text: "TFA 20:1 t [g] (2013)"},
|
||||||
|
{value: 2014, text: "MUFA 22:1 n-9 [g] (2014)"},
|
||||||
|
{value: 2015, text: "MUFA 22:1 n-11 [g] (2015)"},
|
||||||
|
{value: 2016, text: "PUFA 18:2 c [g] (2016)"},
|
||||||
|
{value: 2017, text: "TFA 18:2 t [g] (2017)"},
|
||||||
|
{value: 2018, text: "PUFA 18:3 c [g] (2018)"},
|
||||||
|
{value: 2019, text: "TFA 18:3 t [g] (2019)"},
|
||||||
|
{value: 2020, text: "PUFA 20:3 c [g] (2020)"},
|
||||||
|
{value: 2021, text: "PUFA 22:3 [g] (2021)"},
|
||||||
|
{value: 2022, text: "PUFA 20:4c [g] (2022)"},
|
||||||
|
{value: 2023, text: "PUFA 20:5c [g] (2023)"},
|
||||||
|
{value: 2024, text: "PUFA 22:5 c [g] (2024)"},
|
||||||
|
{value: 2025, text: "PUFA 22:6 c [g] (2025)"},
|
||||||
|
{value: 2026, text: "PUFA 20:2 c [g] (2026)"},
|
||||||
|
{value: 2028, text: "trans-beta-Carotene [µg] (2028)"},
|
||||||
|
{value: 2029, text: "trans-Lycopene [µg] (2029)"},
|
||||||
|
{value: 2032, text: "Cryptoxanthin, alpha [µg] (2032)"},
|
||||||
|
{value: 2033, text: "Total dietary fiber (AOAC 2011.25) [g] (2033)"},
|
||||||
|
{value: 2038, text: "High Molecular Weight Dietary Fiber (HMWDF) [g] (2038)"},
|
||||||
|
{value: 2047, text: "Energy (Atwater General Factors) [kcal] (2047)"},
|
||||||
|
{value: 2048, text: "Energy (Atwater Specific Factors) [kcal] (2048)"},
|
||||||
|
{value: 2049, text: "Daidzin [mg] (2049)"},
|
||||||
|
{value: 2050, text: "Genistin [mg] (2050)"},
|
||||||
|
{value: 2051, text: "Glycitin [mg] (2051)"},
|
||||||
|
{value: 2052, text: "Delta-7-Stigmastenol [mg] (2052)"},
|
||||||
|
{value: 2053, text: "Stigmastadiene [mg] (2053)"},
|
||||||
|
{value: 2057, text: "Ergothioneine [mg] (2057)"},
|
||||||
|
{value: 2058, text: "Beta-glucan [g] (2058)"},
|
||||||
|
{value: 2059, text: "Vitamin D4 [µg] (2059)"},
|
||||||
|
{value: 2060, text: "Ergosta-7-enol [mg] (2060)"},
|
||||||
|
{value: 2061, text: " Ergosta-7,22-dienol [mg] (2061)"},
|
||||||
|
{value: 2062, text: " Ergosta-5,7-dienol [mg] (2062)"},
|
||||||
|
{value: 2063, text: "Verbascose [g] (2063)"},
|
||||||
|
{value: 2065, text: "Low Molecular Weight Dietary Fiber (LMWDF) [g] (2065)"},
|
||||||
|
{value: 2066, text: "Vitamin A [mg] (2066)"},
|
||||||
|
{value: 2069, text: "Glutathione [mg] (2069)"}
|
||||||
|
],
|
||||||
field: "fdc_id",
|
field: "fdc_id",
|
||||||
label: "FDC_ID",
|
label: "FDC_ID",
|
||||||
help_text: "FDC_ID_help",
|
help_text: "FDC_ID_help",
|
||||||
@ -1020,7 +1249,7 @@ export class Actions {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ok_label: { function: "translate", phrase: "Save" },
|
ok_label: {function: "translate", phrase: "Save"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
static UPDATE = {
|
static UPDATE = {
|
||||||
@ -1055,7 +1284,7 @@ export class Actions {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ok_label: { function: "translate", phrase: "Delete" },
|
ok_label: {function: "translate", phrase: "Delete"},
|
||||||
instruction: {
|
instruction: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "instruction",
|
type: "instruction",
|
||||||
@ -1082,17 +1311,17 @@ export class Actions {
|
|||||||
suffix: "s",
|
suffix: "s",
|
||||||
params: ["query", "page", "pageSize", "options"],
|
params: ["query", "page", "pageSize", "options"],
|
||||||
config: {
|
config: {
|
||||||
query: { default: undefined },
|
query: {default: undefined},
|
||||||
page: { default: 1 },
|
page: {default: 1},
|
||||||
pageSize: { default: 25 },
|
pageSize: {default: 25},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
static MERGE = {
|
static MERGE = {
|
||||||
function: "merge",
|
function: "merge",
|
||||||
params: ["source", "target"],
|
params: ["source", "target"],
|
||||||
config: {
|
config: {
|
||||||
source: { type: "string" },
|
source: {type: "string"},
|
||||||
target: { type: "string" },
|
target: {type: "string"},
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
title: {
|
title: {
|
||||||
@ -1107,7 +1336,7 @@ export class Actions {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ok_label: { function: "translate", phrase: "Merge" },
|
ok_label: {function: "translate", phrase: "Merge"},
|
||||||
instruction: {
|
instruction: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "instruction",
|
type: "instruction",
|
||||||
@ -1141,8 +1370,8 @@ export class Actions {
|
|||||||
function: "move",
|
function: "move",
|
||||||
params: ["source", "target"],
|
params: ["source", "target"],
|
||||||
config: {
|
config: {
|
||||||
source: { type: "string" },
|
source: {type: "string"},
|
||||||
target: { type: "string" },
|
target: {type: "string"},
|
||||||
},
|
},
|
||||||
form: {
|
form: {
|
||||||
title: {
|
title: {
|
||||||
@ -1157,7 +1386,7 @@ export class Actions {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
ok_label: { function: "translate", phrase: "Move" },
|
ok_label: {function: "translate", phrase: "Move"},
|
||||||
instruction: {
|
instruction: {
|
||||||
form_field: true,
|
form_field: true,
|
||||||
type: "instruction",
|
type: "instruction",
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -50,6 +50,7 @@ export class StandardToasts {
|
|||||||
static FAIL_DELETE_PROTECTED = "FAIL_DELETE_PROTECTED"
|
static FAIL_DELETE_PROTECTED = "FAIL_DELETE_PROTECTED"
|
||||||
static FAIL_MOVE = "FAIL_MOVE"
|
static FAIL_MOVE = "FAIL_MOVE"
|
||||||
static FAIL_MERGE = "FAIL_MERGE"
|
static FAIL_MERGE = "FAIL_MERGE"
|
||||||
|
static FAIL_IMPORT = "FAIL_IMPORT"
|
||||||
|
|
||||||
static makeStandardToast(context, toast, err = undefined, always_show_errors = false) {
|
static makeStandardToast(context, toast, err = undefined, always_show_errors = false) {
|
||||||
let title = ''
|
let title = ''
|
||||||
@ -122,6 +123,11 @@ export class StandardToasts {
|
|||||||
title = i18n.tc("Failure")
|
title = i18n.tc("Failure")
|
||||||
msg = i18n.tc("err_merging_resource")
|
msg = i18n.tc("err_merging_resource")
|
||||||
break
|
break
|
||||||
|
case StandardToasts.FAIL_IMPORT:
|
||||||
|
variant = 'danger'
|
||||||
|
title = i18n.tc("Failure")
|
||||||
|
msg = i18n.tc("err_importing_recipe")
|
||||||
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -131,12 +137,19 @@ export class StandardToasts {
|
|||||||
console.trace();
|
console.trace();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (err !== undefined && 'response' in err && 'headers' in err.response) {
|
if (err !== undefined
|
||||||
if (DEBUG && err.response.headers['content-type'] === 'application/json' && err.response.status < 500) {
|
&& 'response' in err
|
||||||
|
&& 'headers' in err.response
|
||||||
|
&& err.response.headers['content-type'] === 'application/json'
|
||||||
|
&& err.response.status < 500
|
||||||
|
&& err.response.data) {
|
||||||
|
// If the backend provides us with a nice error message, we print it, regardless of DEBUG mode
|
||||||
|
if (DEBUG || err.response.data.msg) {
|
||||||
|
const errMsg = err.response.data.msg ? err.response.data.msg : JSON.stringify(err.response.data)
|
||||||
msg = context.$createElement('div', {}, [
|
msg = context.$createElement('div', {}, [
|
||||||
context.$createElement('span', {}, [msg]),
|
context.$createElement('span', {}, [msg]),
|
||||||
context.$createElement('br', {}, []),
|
context.$createElement('br', {}, []),
|
||||||
context.$createElement('code', {'class': 'mt-2'}, [JSON.stringify(err.response.data)])
|
context.$createElement('code', {'class': 'mt-2'}, [errMsg])
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,10 @@ const pages = {
|
|||||||
entry: "./src/apps/IngredientEditorView/main.js",
|
entry: "./src/apps/IngredientEditorView/main.js",
|
||||||
chunks: ["chunk-vendors","locales-chunk","api-chunk"],
|
chunks: ["chunk-vendors","locales-chunk","api-chunk"],
|
||||||
},
|
},
|
||||||
|
property_editor_view: {
|
||||||
|
entry: "./src/apps/PropertyEditorView/main.js",
|
||||||
|
chunks: ["chunk-vendors","locales-chunk","api-chunk"],
|
||||||
|
},
|
||||||
shopping_list_view: {
|
shopping_list_view: {
|
||||||
entry: "./src/apps/ShoppingListView/main.js",
|
entry: "./src/apps/ShoppingListView/main.js",
|
||||||
chunks: ["chunk-vendors","locales-chunk","api-chunk"],
|
chunks: ["chunk-vendors","locales-chunk","api-chunk"],
|
||||||
@ -137,7 +141,7 @@ module.exports = {
|
|||||||
|
|
||||||
config.optimization.minimize(true)
|
config.optimization.minimize(true)
|
||||||
|
|
||||||
//TODO somehow remov them as they are also added to the manifest config of the service worker
|
//TODO somehow remove them as they are also added to the manifest config of the service worker
|
||||||
/*
|
/*
|
||||||
Object.keys(pages).forEach(page => {
|
Object.keys(pages).forEach(page => {
|
||||||
config.plugins.delete(`html-${page}`);
|
config.plugins.delete(`html-${page}`);
|
||||||
|
Loading…
Reference in New Issue
Block a user