added recipe properties
This commit is contained in:
parent
763f71a05c
commit
60f31608b9
@ -15,7 +15,7 @@ from .models import (BookmarkletImport, Comment, CookLog, Food, FoodInheritField
|
||||
Recipe, RecipeBook, RecipeBookEntry, RecipeImport, SearchPreference, ShareLink,
|
||||
ShoppingList, ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
|
||||
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync, SyncLog,
|
||||
TelegramBot, Unit, UserFile, UserPreference, ViewLog, Automation, UserSpace, UnitConversion, FoodPropertyType, FoodProperty)
|
||||
TelegramBot, Unit, UserFile, UserPreference, ViewLog, Automation, UserSpace, UnitConversion, PropertyType, FoodProperty)
|
||||
|
||||
|
||||
class CustomUserAdmin(UserAdmin):
|
||||
@ -331,7 +331,7 @@ class FoodPropertyTypeAdmin(admin.ModelAdmin):
|
||||
list_display = ('id', 'name')
|
||||
|
||||
|
||||
admin.site.register(FoodPropertyType, FoodPropertyTypeAdmin)
|
||||
admin.site.register(PropertyType, FoodPropertyTypeAdmin)
|
||||
|
||||
|
||||
class FoodPropertyAdmin(admin.ModelAdmin):
|
||||
|
@ -1,6 +1,6 @@
|
||||
from django.db.models import Q
|
||||
|
||||
from cookbook.models import Unit, SupermarketCategory, FoodProperty, FoodPropertyType, Supermarket, SupermarketCategoryRelation, Food, Automation, UnitConversion
|
||||
from cookbook.models import Unit, SupermarketCategory, FoodProperty, PropertyType, Supermarket, SupermarketCategoryRelation, Food, Automation, UnitConversion
|
||||
|
||||
|
||||
class OpenDataImporter:
|
||||
@ -55,14 +55,14 @@ class OpenDataImporter:
|
||||
|
||||
insert_list = []
|
||||
for k in list(self.data[datatype].keys()):
|
||||
insert_list.append(FoodPropertyType(
|
||||
insert_list.append(PropertyType(
|
||||
name=self.data[datatype][k]['name'],
|
||||
unit=self.data[datatype][k]['unit'],
|
||||
open_data_slug=k,
|
||||
space=self.request.space
|
||||
))
|
||||
|
||||
return FoodPropertyType.objects.bulk_create(insert_list, update_conflicts=True, update_fields=('open_data_slug',), unique_fields=('space', 'name',))
|
||||
return PropertyType.objects.bulk_create(insert_list, update_conflicts=True, update_fields=('open_data_slug',), unique_fields=('space', 'name',))
|
||||
|
||||
def import_supermarket(self):
|
||||
datatype = 'supermarket'
|
||||
@ -114,7 +114,7 @@ class OpenDataImporter:
|
||||
existing_objects[f[2]] = f
|
||||
|
||||
self._update_slug_cache(Unit, 'unit')
|
||||
self._update_slug_cache(FoodPropertyType, 'property')
|
||||
self._update_slug_cache(PropertyType, 'property')
|
||||
|
||||
pref_unit_key = 'preferred_unit_metric'
|
||||
pref_shopping_unit_key = 'preferred_packaging_unit_metric'
|
||||
|
@ -1,4 +1,4 @@
|
||||
from cookbook.models import FoodPropertyType, Unit, Food, FoodProperty, Recipe, Step
|
||||
from cookbook.models import PropertyType, Unit, Food, FoodProperty, Recipe, Step
|
||||
|
||||
|
||||
class FoodPropertyHelper:
|
||||
@ -19,7 +19,7 @@ class FoodPropertyHelper:
|
||||
"""
|
||||
ingredients = []
|
||||
computed_properties = {}
|
||||
property_types = FoodPropertyType.objects.filter(space=self.space).all()
|
||||
property_types = PropertyType.objects.filter(space=self.space).all()
|
||||
|
||||
for s in recipe.steps.all():
|
||||
ingredients += s.ingredients.all()
|
||||
@ -64,10 +64,10 @@ class FoodPropertyHelper:
|
||||
food_1 = Food.objects.create(name='Food 1', space=self.space)
|
||||
food_2 = Food.objects.create(name='Food 2', space=self.space)
|
||||
|
||||
property_fat = FoodPropertyType.objects.create(name='Fat', unit='g', space=self.space)
|
||||
property_calories = FoodPropertyType.objects.create(name='Calories', unit='kcal', space=self.space)
|
||||
property_nuts = FoodPropertyType.objects.create(name='Nuts', space=self.space)
|
||||
property_price = FoodPropertyType.objects.create(name='Price', unit='€', space=self.space)
|
||||
property_fat = PropertyType.objects.create(name='Fat', unit='g', space=self.space)
|
||||
property_calories = PropertyType.objects.create(name='Calories', unit='kcal', space=self.space)
|
||||
property_nuts = PropertyType.objects.create(name='Nuts', space=self.space)
|
||||
property_price = PropertyType.objects.create(name='Price', unit='€', space=self.space)
|
||||
|
||||
food_1_property_fat = FoodProperty.objects.create(food_amount=100, food_unit=unit_gram, food=food_1, property_amount=50, property_type=property_fat, space=self.space)
|
||||
food_1_property_nuts = FoodProperty.objects.create(food_amount=100, food_unit=unit_gram, food=food_1, property_amount=1, property_type=property_nuts, space=self.space)
|
||||
@ -87,3 +87,18 @@ class FoodPropertyHelper:
|
||||
step_2 = Step.objects.create(instruction='instruction_step_1', space=self.space)
|
||||
step_2.ingredients.create(amount=50, unit=unit_gram, food=food_1, space=self.space)
|
||||
recipe_1.steps.add(step_2)
|
||||
|
||||
|
||||
class RecipePropertyHelper:
|
||||
space = None
|
||||
|
||||
def __init__(self, space):
|
||||
"""
|
||||
Helper to perform recipe property operations
|
||||
:param space: space to limit scope to
|
||||
"""
|
||||
self.space = space
|
||||
|
||||
|
||||
def parse_properties_from_schema(self, schema):
|
||||
pass
|
@ -1,5 +1,6 @@
|
||||
# import random
|
||||
import re
|
||||
import traceback
|
||||
from html import unescape
|
||||
|
||||
from django.core.cache import caches
|
||||
@ -12,7 +13,8 @@ from recipe_scrapers._utils import get_host_name, get_minutes
|
||||
|
||||
# from cookbook.helper import recipe_url_import as helper
|
||||
from cookbook.helper.ingredient_parser import IngredientParser
|
||||
from cookbook.models import Automation, Keyword
|
||||
from cookbook.models import Automation, Keyword, PropertyType
|
||||
|
||||
|
||||
# from unicodedata import decomposition
|
||||
|
||||
@ -193,6 +195,13 @@ def get_from_scraper(scrape, request):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
try:
|
||||
recipe_json['properties'] = get_recipe_properties(request.space, scrape.schema.nutrients())
|
||||
print(recipe_json['properties'])
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
pass
|
||||
|
||||
if recipe_json['source_url']:
|
||||
automations = Automation.objects.filter(type=Automation.INSTRUCTION_REPLACE, space=request.space, disabled=False).only('param_1', 'param_2', 'param_3').order_by('order').all()[:512]
|
||||
for a in automations:
|
||||
@ -203,6 +212,30 @@ def get_from_scraper(scrape, request):
|
||||
return recipe_json
|
||||
|
||||
|
||||
def get_recipe_properties(space, property_data):
|
||||
# {'servingSize': '1', 'calories': '302 kcal', 'proteinContent': '7,66g', 'fatContent': '11,56g', 'carbohydrateContent': '41,33g'}
|
||||
properties = {
|
||||
"property-calories": "calories",
|
||||
"property-carbohydrates": "carbohydrateContent",
|
||||
"property-proteins": "proteinContent",
|
||||
"property-fats": "fatContent",
|
||||
}
|
||||
recipe_properties = []
|
||||
for pt in PropertyType.objects.filter(space=space, open_data_slug__in=list(properties.keys())).all():
|
||||
for p in list(properties.keys()):
|
||||
if pt.open_data_slug == p:
|
||||
if properties[p] in property_data:
|
||||
recipe_properties.append({
|
||||
'property_type': {
|
||||
'id': pt.id,
|
||||
'name': pt.name,
|
||||
},
|
||||
'property_amount': parse_servings(property_data[properties[p]]) / float(property_data['servingSize']),
|
||||
})
|
||||
|
||||
return recipe_properties
|
||||
|
||||
|
||||
def get_from_youtube_scraper(url, request):
|
||||
"""A YouTube Information Scraper."""
|
||||
kw, created = Keyword.objects.get_or_create(name='YouTube', space=request.space)
|
||||
|
@ -0,0 +1,34 @@
|
||||
# Generated by Django 4.1.7 on 2023-05-06 16:33
|
||||
|
||||
import cookbook.models
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('cookbook', '0194_supermarketcategoryrelation_unique_sm_category_relation'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameModel(
|
||||
old_name='FoodPropertyType',
|
||||
new_name='PropertyType',
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='RecipeProperty',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('property_amount', models.DecimalField(decimal_places=4, default=0, max_digits=32)),
|
||||
('property_type', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='cookbook.propertytype')),
|
||||
('space', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='cookbook.space')),
|
||||
],
|
||||
bases=(models.Model, cookbook.models.PermissionModelMixin),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='recipe',
|
||||
name='properties',
|
||||
field=models.ManyToManyField(blank=True, to='cookbook.recipeproperty'),
|
||||
),
|
||||
]
|
@ -755,7 +755,7 @@ class Step(ExportModelOperationsMixin('step'), models.Model, PermissionModelMixi
|
||||
indexes = (GinIndex(fields=["search_vector"]),)
|
||||
|
||||
|
||||
class FoodPropertyType(models.Model, PermissionModelMixin):
|
||||
class PropertyType(models.Model, PermissionModelMixin):
|
||||
NUTRITION = 'NUTRITION'
|
||||
ALLERGEN = 'ALLERGEN'
|
||||
PRICE = 'PRICE'
|
||||
@ -780,7 +780,7 @@ class FoodPropertyType(models.Model, PermissionModelMixin):
|
||||
|
||||
class Meta:
|
||||
constraints = [
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='food_property_type_unique_name_per_space')
|
||||
models.UniqueConstraint(fields=['space', 'name'], name='property_type_unique_name_per_space')
|
||||
]
|
||||
|
||||
|
||||
@ -789,7 +789,7 @@ class FoodProperty(models.Model, PermissionModelMixin):
|
||||
food_unit = models.ForeignKey(Unit, on_delete=models.CASCADE)
|
||||
food = models.ForeignKey(Food, on_delete=models.CASCADE)
|
||||
property_amount = models.DecimalField(default=0, decimal_places=4, max_digits=32)
|
||||
property_type = models.ForeignKey(FoodPropertyType, on_delete=models.PROTECT)
|
||||
property_type = models.ForeignKey(PropertyType, on_delete=models.PROTECT)
|
||||
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space')
|
||||
@ -803,6 +803,17 @@ class FoodProperty(models.Model, PermissionModelMixin):
|
||||
]
|
||||
|
||||
|
||||
class RecipeProperty(models.Model, PermissionModelMixin):
|
||||
property_amount = models.DecimalField(default=0, decimal_places=4, max_digits=32)
|
||||
property_type = models.ForeignKey(PropertyType, on_delete=models.PROTECT)
|
||||
|
||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
||||
objects = ScopedManager(space='space')
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.property_amount} {self.property_type.unit} {self.property_type.name}'
|
||||
|
||||
|
||||
class NutritionInformation(models.Model, PermissionModelMixin):
|
||||
fats = models.DecimalField(default=0, decimal_places=16, max_digits=32)
|
||||
carbohydrates = models.DecimalField(
|
||||
@ -841,6 +852,7 @@ class Recipe(ExportModelOperationsMixin('recipe'), models.Model, PermissionModel
|
||||
waiting_time = models.IntegerField(default=0)
|
||||
internal = models.BooleanField(default=False)
|
||||
nutrition = models.ForeignKey(NutritionInformation, blank=True, null=True, on_delete=models.CASCADE)
|
||||
properties = models.ManyToManyField(RecipeProperty, blank=True)
|
||||
show_ingredient_overview = models.BooleanField(default=True)
|
||||
private = models.BooleanField(default=False)
|
||||
shared = models.ManyToManyField(User, blank=True, related_name='recipe_shared_with')
|
||||
|
@ -22,7 +22,7 @@ from rest_framework.exceptions import NotFound, ValidationError
|
||||
|
||||
from cookbook.helper.CustomStorageClass import CachedS3Boto3Storage
|
||||
from cookbook.helper.HelperFunctions import str2bool
|
||||
from cookbook.helper.food_property_helper import FoodPropertyHelper
|
||||
from cookbook.helper.property_helper import FoodPropertyHelper
|
||||
from cookbook.helper.permission_helper import above_space_limit
|
||||
from cookbook.helper.shopping_helper import RecipeShoppingEditor
|
||||
from cookbook.helper.unit_conversion_helper import UnitConversionHelper
|
||||
@ -33,7 +33,7 @@ from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Cu
|
||||
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
|
||||
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
|
||||
SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog, UnitConversion, FoodProperty,
|
||||
FoodPropertyType)
|
||||
PropertyType, RecipeProperty)
|
||||
from cookbook.templatetags.custom_tags import markdown
|
||||
from recipes.settings import AWS_ENABLED, MEDIA_URL
|
||||
|
||||
@ -747,18 +747,18 @@ class UnitConversionSerializer(WritableNestedModelSerializer):
|
||||
fields = ('id', 'name', 'base_amount', 'base_unit', 'converted_amount', 'converted_unit', 'food', 'open_data_slug')
|
||||
|
||||
|
||||
class FoodPropertyTypeSerializer(serializers.ModelSerializer):
|
||||
class PropertyTypeSerializer(serializers.ModelSerializer):
|
||||
def create(self, validated_data):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
return super().create(validated_data)
|
||||
|
||||
class Meta:
|
||||
model = FoodPropertyType
|
||||
model = PropertyType
|
||||
fields = ('id', 'name', 'icon', 'unit', 'description', 'open_data_slug')
|
||||
|
||||
|
||||
class FoodPropertySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||
property_type = FoodPropertyTypeSerializer()
|
||||
property_type = PropertyTypeSerializer()
|
||||
food = FoodSimpleSerializer()
|
||||
food_unit = UnitSerializer()
|
||||
food_amount = CustomDecimalField()
|
||||
@ -776,6 +776,20 @@ class FoodPropertySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||
read_only_fields = ('id',)
|
||||
|
||||
|
||||
class RecipePropertySerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||
property_type = PropertyTypeSerializer()
|
||||
property_amount = CustomDecimalField()
|
||||
|
||||
def create(self, validated_data):
|
||||
validated_data['space'] = self.context['request'].space
|
||||
return super().create(validated_data)
|
||||
|
||||
class Meta:
|
||||
model = RecipeProperty
|
||||
fields = ('id', 'property_type', 'property_amount',)
|
||||
read_only_fields = ('id',)
|
||||
|
||||
|
||||
class NutritionInformationSerializer(serializers.ModelSerializer):
|
||||
carbohydrates = CustomDecimalField()
|
||||
fats = CustomDecimalField()
|
||||
@ -826,6 +840,7 @@ class RecipeOverviewSerializer(RecipeBaseSerializer):
|
||||
|
||||
class RecipeSerializer(RecipeBaseSerializer):
|
||||
nutrition = NutritionInformationSerializer(allow_null=True, required=False)
|
||||
properties = RecipePropertySerializer(many=True, required=False)
|
||||
steps = StepSerializer(many=True)
|
||||
keywords = KeywordSerializer(many=True)
|
||||
shared = UserSerializer(many=True, required=False)
|
||||
@ -842,7 +857,7 @@ class RecipeSerializer(RecipeBaseSerializer):
|
||||
fields = (
|
||||
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time',
|
||||
'waiting_time', 'created_by', 'created_at', 'updated_at', 'source_url',
|
||||
'internal', 'show_ingredient_overview', 'nutrition', 'food_properties', 'servings', 'file_path', 'servings_text', 'rating',
|
||||
'internal', 'show_ingredient_overview', 'nutrition', 'properties', 'food_properties', 'servings', 'file_path', 'servings_text', 'rating',
|
||||
'last_cooked',
|
||||
'private', 'shared',
|
||||
)
|
||||
|
@ -270,7 +270,7 @@
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<a href="{% url 'list_food_property_type' %}" class="p-0 p-md-1">
|
||||
<a href="{% url 'list_property_type' %}" class="p-0 p-md-1">
|
||||
<div class="card p-0 no-gutters border-0">
|
||||
<div class="card-body text-center p-0 no-gutters">
|
||||
<i class="fas fa-database fa-2x"></i>
|
||||
|
@ -1,8 +1,8 @@
|
||||
from django.contrib import auth
|
||||
from django_scopes import scopes_disabled
|
||||
|
||||
from cookbook.helper.food_property_helper import FoodPropertyHelper
|
||||
from cookbook.models import Unit, Food, FoodPropertyType, FoodProperty, Recipe, Step
|
||||
from cookbook.helper.property_helper import FoodPropertyHelper
|
||||
from cookbook.models import Unit, Food, PropertyType, FoodProperty, Recipe, Step
|
||||
|
||||
|
||||
def test_food_property(space_1, u1_s1):
|
||||
@ -16,10 +16,10 @@ def test_food_property(space_1, u1_s1):
|
||||
food_1 = Food.objects.create(name='food_1', space=space_1)
|
||||
food_2 = Food.objects.create(name='food_2', space=space_1)
|
||||
|
||||
property_fat = FoodPropertyType.objects.create(name='property_fat', space=space_1)
|
||||
property_calories = FoodPropertyType.objects.create(name='property_calories', space=space_1)
|
||||
property_nuts = FoodPropertyType.objects.create(name='property_nuts', space=space_1)
|
||||
property_price = FoodPropertyType.objects.create(name='property_price', space=space_1)
|
||||
property_fat = PropertyType.objects.create(name='property_fat', space=space_1)
|
||||
property_calories = PropertyType.objects.create(name='property_calories', space=space_1)
|
||||
property_nuts = PropertyType.objects.create(name='property_nuts', space=space_1)
|
||||
property_price = PropertyType.objects.create(name='property_price', space=space_1)
|
||||
|
||||
food_1_property_fat = FoodProperty.objects.create(food_amount=100, food_unit=unit_gram, food=food_1, property_amount=50, property_type=property_fat, space=space_1)
|
||||
food_1_property_nuts = FoodProperty.objects.create(food_amount=100, food_unit=unit_gram, food=food_1, property_amount=1, property_type=property_nuts, space=space_1)
|
||||
|
@ -12,7 +12,7 @@ from recipes.version import VERSION_NUMBER
|
||||
from .models import (Automation, Comment, CustomFilter, Food, InviteLink, Keyword, MealPlan, Recipe,
|
||||
RecipeBook, RecipeBookEntry, RecipeImport, ShoppingList, Step, Storage,
|
||||
Supermarket, SupermarketCategory, Sync, SyncLog, Unit, UserFile,
|
||||
get_model_name, UserSpace, Space, FoodPropertyType, UnitConversion)
|
||||
get_model_name, UserSpace, Space, PropertyType, UnitConversion)
|
||||
from .views import api, data, delete, edit, import_export, lists, new, telegram, views
|
||||
from .views.api import CustomAuthToken, ImportOpenData
|
||||
|
||||
@ -193,7 +193,7 @@ for m in generic_models:
|
||||
)
|
||||
)
|
||||
|
||||
vue_models = [Food, Keyword, Unit, Supermarket, SupermarketCategory, Automation, UserFile, Step, CustomFilter, UnitConversion, FoodPropertyType]
|
||||
vue_models = [Food, Keyword, Unit, Supermarket, SupermarketCategory, Automation, UserFile, Step, CustomFilter, UnitConversion, PropertyType]
|
||||
for m in vue_models:
|
||||
py_name = get_model_name(m)
|
||||
url_name = py_name.replace('_', '-')
|
||||
|
@ -70,7 +70,7 @@ from cookbook.models import (Automation, BookmarkletImport, CookLog, CustomFilte
|
||||
MealType, Recipe, RecipeBook, RecipeBookEntry, ShareLink, ShoppingList,
|
||||
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
|
||||
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
|
||||
SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog, UnitConversion, FoodPropertyType, FoodProperty)
|
||||
SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog, UnitConversion, PropertyType, FoodProperty)
|
||||
from cookbook.provider.dropbox import Dropbox
|
||||
from cookbook.provider.local import Local
|
||||
from cookbook.provider.nextcloud import Nextcloud
|
||||
@ -94,7 +94,7 @@ from cookbook.serializer import (AutomationSerializer, BookmarkletImportListSeri
|
||||
SyncLogSerializer, SyncSerializer, UnitSerializer,
|
||||
UserFileSerializer, UserSerializer, UserPreferenceSerializer,
|
||||
UserSpaceSerializer, ViewLogSerializer, AccessTokenSerializer, FoodSimpleSerializer,
|
||||
RecipeExportSerializer, UnitConversionSerializer, FoodPropertyTypeSerializer, FoodPropertySerializer)
|
||||
RecipeExportSerializer, UnitConversionSerializer, PropertyTypeSerializer, FoodPropertySerializer)
|
||||
from cookbook.views.import_export import get_integration
|
||||
from recipes import settings
|
||||
|
||||
@ -811,8 +811,12 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
||||
|
||||
if self.detail: # if detail request and not list, private condition is verified by permission class
|
||||
if not share: # filter for space only if not shared
|
||||
self.queryset = self.queryset.filter(space=self.request.space).prefetch_related('steps', 'keywords',
|
||||
self.queryset = self.queryset.filter(space=self.request.space).prefetch_related(
|
||||
'keywords',
|
||||
'shared',
|
||||
'properties',
|
||||
'properties__property_type',
|
||||
'steps',
|
||||
'steps__ingredients',
|
||||
'steps__ingredients__step_set',
|
||||
'steps__ingredients__step_set__recipe_set',
|
||||
@ -831,9 +835,8 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
||||
'steps__ingredients__unit__unit_conversion_base_relation__base_unit',
|
||||
'steps__ingredients__unit__unit_conversion_converted_relation',
|
||||
'steps__ingredients__unit__unit_conversion_converted_relation__converted_unit',
|
||||
'cooklog_set').select_related('nutrition')
|
||||
|
||||
'cooklog_set').select_related(
|
||||
'nutrition')
|
||||
return super().get_queryset()
|
||||
|
||||
self.queryset = self.queryset.filter(space=self.request.space).filter(
|
||||
@ -972,8 +975,8 @@ class UnitConversionViewSet(viewsets.ModelViewSet):
|
||||
|
||||
|
||||
class FoodPropertyTypeViewSet(viewsets.ModelViewSet):
|
||||
queryset = FoodPropertyType.objects
|
||||
serializer_class = FoodPropertyTypeSerializer
|
||||
queryset = PropertyType.objects
|
||||
serializer_class = PropertyTypeSerializer
|
||||
permission_classes = [CustomIsUser & CustomTokenHasReadWriteScope]
|
||||
|
||||
def get_queryset(self):
|
||||
|
@ -246,15 +246,15 @@ def unit_conversion(request):
|
||||
|
||||
|
||||
@group_required('user')
|
||||
def food_property_type(request):
|
||||
def property_type(request):
|
||||
# model-name is the models.js name of the model, probably ALL-CAPS
|
||||
return render(
|
||||
request,
|
||||
'generic/model_template.html',
|
||||
{
|
||||
"title": _("Food Property Types"),
|
||||
"title": _("Property Types"),
|
||||
"config": {
|
||||
'model': "FOOD_PROPERTY_TYPE", # *REQUIRED* name of the model in models.js
|
||||
'model': "PROPERTY_TYPE", # *REQUIRED* name of the model in models.js
|
||||
}
|
||||
}
|
||||
)
|
||||
|
@ -23,7 +23,7 @@ from oauth2_provider.models import AccessToken
|
||||
from cookbook.forms import (CommentForm, Recipe, SearchPreferenceForm, ShoppingPreferenceForm,
|
||||
SpaceCreateForm, SpaceJoinForm, User,
|
||||
UserCreateForm, UserNameForm, UserPreference, UserPreferenceForm)
|
||||
from cookbook.helper.food_property_helper import FoodPropertyHelper
|
||||
from cookbook.helper.property_helper import FoodPropertyHelper
|
||||
from cookbook.helper.permission_helper import group_required, has_group_permission, share_link_valid, switch_user_active_space
|
||||
from cookbook.models import (Comment, CookLog, InviteLink, SearchFields, SearchPreference, ShareLink,
|
||||
Space, ViewLog, UserSpace)
|
||||
|
@ -31,7 +31,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Image and misc properties -->
|
||||
<!-- Image and misc -->
|
||||
<div class="row pt-2">
|
||||
<div class="col-md-6" style="max-height: 50vh; min-height: 30vh">
|
||||
<input id="id_file_upload" ref="file_upload" type="file" hidden
|
||||
@ -99,65 +99,53 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nutrition -->
|
||||
<div class="row pt-2">
|
||||
<div class="col-md-12">
|
||||
<div class="card border-grey">
|
||||
<div class="card-header" style="display: table">
|
||||
<div class="row">
|
||||
<div class="col-md-9 d-table">
|
||||
<h5 class="d-table-cell align-middle">{{ $t("Nutrition") }}</h5>
|
||||
<div class="card mt-2 mb-2">
|
||||
<div class="card-body pr-2 pl-2 pr-md-5 pl-md-5 pt-3 pb-3">
|
||||
<h6>{{ $t('Properties') }} <small class="text-muted"> {{$t('per_serving')}}</small></h6>
|
||||
|
||||
<div class="alert alert-info" role="alert">
|
||||
{{ $t('recipe_property_info')}}
|
||||
</div>
|
||||
|
||||
<div class="d-flex mt-2" v-for="p in recipe.properties" v-bind:key="p.id">
|
||||
<div class="flex-fill w-50">
|
||||
<generic-multiselect
|
||||
@change="p.property_type = $event.val"
|
||||
:initial_single_selection="p.property_type"
|
||||
:label="'name'"
|
||||
:model="Models.PROPERTY_TYPE"
|
||||
:limit="25"
|
||||
:multiple="false"
|
||||
></generic-multiselect>
|
||||
</div>
|
||||
<div class="flex-fill w-50">
|
||||
<div class="input-group">
|
||||
<input type="number" class="form-control" v-model="p.property_amount">
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text" v-if="p.property_type !== null && p.property_type.unit !== ''">{{ p.property_type.unit }}</span>
|
||||
<button class="btn btn-danger" @click="deleteProperty(p)"><i class="fa fa-trash fa-fw"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex-row mt-2">
|
||||
<div class="flex-column w-25 offset-4">
|
||||
<button class="btn btn-success btn-block" @click="addProperty()"><i class="fa fa-plus"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<button
|
||||
type="button"
|
||||
@click="addNutrition()"
|
||||
v-if="recipe.nutrition === null"
|
||||
v-b-tooltip.hover
|
||||
v-bind:title="$t('Add_nutrition_recipe')"
|
||||
class="btn btn-sm btn-success shadow-none float-right"
|
||||
>
|
||||
<i class="fas fa-plus-circle"></i>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
@click="removeNutrition()"
|
||||
v-if="recipe.nutrition !== null"
|
||||
v-b-tooltip.hover
|
||||
v-bind:title="$t('Remove_nutrition_recipe')"
|
||||
class="btn btn-sm btn-danger shadow-none float-right"
|
||||
>
|
||||
<i class="fas fa-trash-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<b-collapse id="id_nutrition_collapse" class="mt-2" v-model="nutrition_visible">
|
||||
<div class="card-body" v-if="recipe.nutrition !== null">
|
||||
<b-alert show>
|
||||
There is currently only very basic support for tracking nutritional information. A
|
||||
<a href="https://github.com/vabene1111/recipes/issues/896" target="_blank"
|
||||
rel="noreferrer nofollow">big update</a> is planned to improve on this in many
|
||||
different areas.
|
||||
</b-alert>
|
||||
<div class="row pt-2">
|
||||
<div class="col-md-12">
|
||||
|
||||
<label for="id_name"> {{ $t(energy()) }}</label>
|
||||
|
||||
<input class="form-control" id="id_calories" v-model="recipe.nutrition.calories"/>
|
||||
|
||||
<label for="id_name"> {{ $t("Carbohydrates") }}</label>
|
||||
<input class="form-control" id="id_carbohydrates"
|
||||
v-model="recipe.nutrition.carbohydrates"/>
|
||||
|
||||
<label for="id_name"> {{ $t("Fats") }}</label>
|
||||
<input class="form-control" id="id_fats" v-model="recipe.nutrition.fats"/>
|
||||
|
||||
<label for="id_name"> {{ $t("Proteins") }}</label>
|
||||
<input class="form-control" id="id_proteins" v-model="recipe.nutrition.proteins"/>
|
||||
</div>
|
||||
</b-collapse>
|
||||
</div>
|
||||
<b-card-header header-tag="header" class="p-1" role="tab">
|
||||
<b-button squared block v-b-toggle.additional_collapse class="text-left"
|
||||
variant="outline-primary">{{ $t("additional_options") }}
|
||||
@ -1121,6 +1109,14 @@ export default {
|
||||
let new_keyword = {label: tag, name: tag}
|
||||
this.recipe.keywords.push(new_keyword)
|
||||
},
|
||||
addProperty: function () {
|
||||
this.recipe.properties.push(
|
||||
{'property_amount': 0, 'property_type': null}
|
||||
)
|
||||
},
|
||||
deleteProperty: function (recipe_property) {
|
||||
this.recipe.properties = this.recipe.properties.filter(p => p.id !== recipe_property.id)
|
||||
},
|
||||
searchKeywords: function (query) {
|
||||
let apiFactory = new ApiApiFactory()
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
"success_moving_resource": "Successfully moved a resource!",
|
||||
"success_merging_resource": "Successfully merged a resource!",
|
||||
"file_upload_disabled": "File upload is not enabled for your space.",
|
||||
"recipe_property_info": "You can also add properties to foods to calculate them automatically based on your recipe!",
|
||||
"warning_space_delete": "You can delete your space including all recipes, shopping lists, meal plans and whatever else you have created. This cannot be undone! Are you sure you want to do this ?",
|
||||
"food_inherit_info": "Fields on food that should be inherited by default.",
|
||||
"facet_count_info": "Show recipe counts on search filters.",
|
||||
@ -37,6 +38,7 @@
|
||||
"Add_nutrition_recipe": "Add nutrition to recipe",
|
||||
"Remove_nutrition_recipe": "Delete nutrition from recipe",
|
||||
"Copy_template_reference": "Copy template reference",
|
||||
"per_serving": "per servings",
|
||||
"Save_and_View": "Save & View",
|
||||
"Manage_Books": "Manage Books",
|
||||
"Meal_Plan": "Meal Plan",
|
||||
|
@ -659,9 +659,9 @@ export class Models {
|
||||
},
|
||||
}
|
||||
|
||||
static FOOD_PROPERTY_TYPE = {
|
||||
name: "Food Property Type",
|
||||
apiName: "FoodPropertyType",
|
||||
static PROPERTY_TYPE = {
|
||||
name: "Property Type",
|
||||
apiName: "PropertyType",
|
||||
paginated: false,
|
||||
list: {
|
||||
header_component: {
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user