optimized unit conversion queries
using filter breaks prefetch related
This commit is contained in:
parent
c217bf2445
commit
38010117e5
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-02-26 06:30
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0191_foodnutrition_food_nutrition_unique_per_space_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='unitconversion',
|
||||||
|
name='base_unit',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_conversion_base_unit', to='cookbook.unit'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='unitconversion',
|
||||||
|
name='converted_unit',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_conversion_converted_unit', to='cookbook.unit'),
|
||||||
|
),
|
||||||
|
]
|
@ -0,0 +1,24 @@
|
|||||||
|
# Generated by Django 4.1.7 on 2023-02-26 07:03
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('cookbook', '0192_alter_unitconversion_base_unit_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='unitconversion',
|
||||||
|
name='base_unit',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_conversion_base_relation', to='cookbook.unit'),
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='unitconversion',
|
||||||
|
name='converted_unit',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='unit_conversion_converted_relation', to='cookbook.unit'),
|
||||||
|
),
|
||||||
|
]
|
@ -652,9 +652,9 @@ class Food(ExportModelOperationsMixin('food'), TreeModel, PermissionModelMixin):
|
|||||||
|
|
||||||
class UnitConversion(ExportModelOperationsMixin('unit_conversion'), models.Model, PermissionModelMixin):
|
class UnitConversion(ExportModelOperationsMixin('unit_conversion'), models.Model, PermissionModelMixin):
|
||||||
base_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
|
base_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
|
||||||
base_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='base_unit')
|
base_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='unit_conversion_base_relation')
|
||||||
converted_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
|
converted_amount = models.DecimalField(default=0, decimal_places=16, max_digits=32)
|
||||||
converted_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='converted_unit')
|
converted_unit = models.ForeignKey('Unit', on_delete=models.CASCADE, related_name='unit_conversion_converted_relation')
|
||||||
|
|
||||||
food = models.ForeignKey('Food', on_delete=models.CASCADE, null=True, blank=True)
|
food = models.ForeignKey('Food', on_delete=models.CASCADE, null=True, blank=True)
|
||||||
|
|
||||||
|
@ -29,7 +29,8 @@ from cookbook.models import (Automation, BookmarkletImport, Comment, CookLog, Cu
|
|||||||
RecipeBookEntry, RecipeImport, ShareLink, ShoppingList,
|
RecipeBookEntry, RecipeImport, ShareLink, ShoppingList,
|
||||||
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
|
ShoppingListEntry, ShoppingListRecipe, Space, Step, Storage,
|
||||||
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
|
Supermarket, SupermarketCategory, SupermarketCategoryRelation, Sync,
|
||||||
SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog, UnitConversion, FoodNutrition, NutritionType)
|
SyncLog, Unit, UserFile, UserPreference, UserSpace, ViewLog, UnitConversion, FoodNutrition,
|
||||||
|
NutritionType)
|
||||||
from cookbook.templatetags.custom_tags import markdown
|
from cookbook.templatetags.custom_tags import markdown
|
||||||
from recipes.settings import AWS_ENABLED, MEDIA_URL
|
from recipes.settings import AWS_ENABLED, MEDIA_URL
|
||||||
|
|
||||||
@ -276,10 +277,12 @@ class SpaceSerializer(WritableNestedModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Space
|
model = Space
|
||||||
fields = ('id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users',
|
fields = (
|
||||||
'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb',
|
'id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users',
|
||||||
'image', 'use_plural',)
|
'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb',
|
||||||
read_only_fields = ('id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',)
|
'image', 'use_plural',)
|
||||||
|
read_only_fields = (
|
||||||
|
'id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',)
|
||||||
|
|
||||||
|
|
||||||
class UserSpaceSerializer(WritableNestedModelSerializer):
|
class UserSpaceSerializer(WritableNestedModelSerializer):
|
||||||
@ -440,7 +443,8 @@ class UnitSerializer(UniqueFieldsMixin, ExtendedRecipeMixin):
|
|||||||
return unit
|
return unit
|
||||||
|
|
||||||
space = validated_data.pop('space', self.context['request'].space)
|
space = validated_data.pop('space', self.context['request'].space)
|
||||||
obj, created = Unit.objects.get_or_create(name=name, plural_name=plural_name, space=space, defaults=validated_data)
|
obj, created = Unit.objects.get_or_create(name=name, plural_name=plural_name, space=space,
|
||||||
|
defaults=validated_data)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
@ -579,7 +583,8 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer, ExtendedR
|
|||||||
else:
|
else:
|
||||||
validated_data['onhand_users'] = list(set(onhand_users) - set(shared_users))
|
validated_data['onhand_users'] = list(set(onhand_users) - set(shared_users))
|
||||||
|
|
||||||
obj, created = Food.objects.get_or_create(name=name, plural_name=plural_name, space=space, defaults=validated_data)
|
obj, created = Food.objects.get_or_create(name=name, plural_name=plural_name, space=space,
|
||||||
|
defaults=validated_data)
|
||||||
return obj
|
return obj
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
@ -626,35 +631,35 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
|
|||||||
|
|
||||||
def get_conversions(self, obj):
|
def get_conversions(self, obj):
|
||||||
conversions = []
|
conversions = []
|
||||||
|
# TODO add hardcoded base conversions for metric/imperial
|
||||||
for c in UnitConversion.objects.filter(space=self.context['request'].space).filter(Q(food__isnull=True) | Q(food=obj.food)).filter(Q(base_unit=obj.unit) | Q(converted_unit=obj.unit)):
|
if obj.unit: # TODO move to function and also iterate obj.unit.unit_conversion_converted_relation.all()
|
||||||
if obj.unit == c.base_unit:
|
for c in obj.unit.unit_conversion_base_relation.all():
|
||||||
conversions.append({
|
if c.food is None or c.food == obj.food:
|
||||||
'amount': obj.amount * (c.converted_amount / c.base_amount),
|
if obj.unit == c.base_unit:
|
||||||
'unit': UnitSerializer(c.converted_unit, context={'request': self.context['request']}).data,
|
conversions.append({
|
||||||
})
|
'amount': obj.amount * (c.converted_amount / c.base_amount),
|
||||||
else:
|
'unit': UnitSerializer(c.converted_unit, context={'request': self.context['request']}).data,
|
||||||
conversions.append({
|
})
|
||||||
'amount': obj.amount * (c.base_amount / c.converted_amount),
|
else:
|
||||||
'unit': UnitSerializer(c.base_unit, context={'request': self.context['request']}).data,
|
conversions.append({
|
||||||
})
|
'amount': obj.amount * (c.base_amount / c.converted_amount),
|
||||||
|
'unit': UnitSerializer(c.base_unit, context={'request': self.context['request']}).data,
|
||||||
|
})
|
||||||
|
|
||||||
return conversions
|
return conversions
|
||||||
|
|
||||||
def get_nutritions(self, ingredient):
|
def get_nutritions(self, ingredient):
|
||||||
nutritions = {}
|
nutritions = {}
|
||||||
for nt in NutritionType.objects.filter(space=self.context['request'].space).all():
|
|
||||||
nutritions[nt.id] = None
|
|
||||||
|
|
||||||
food_nutrition = ingredient.food.foodnutrition_set.all()
|
if ingredient.food:
|
||||||
for fn in food_nutrition:
|
for fn in ingredient.food.foodnutrition_set.all():
|
||||||
if fn.food_unit == ingredient.unit:
|
if fn.food_unit == ingredient.unit:
|
||||||
nutritions[fn.nutrition_type.id] = ingredient.amount / fn.food_amount * fn.nutrition_amount
|
nutritions[fn.nutrition_type.id] = ingredient.amount / fn.food_amount * fn.nutrition_amount
|
||||||
else:
|
else:
|
||||||
conversions = self.get_conversions(ingredient)
|
conversions = self.get_conversions(ingredient)
|
||||||
for c in conversions:
|
for c in conversions:
|
||||||
if fn.food_unit.id == c['unit']['id']:
|
if fn.food_unit.id == c['unit']['id']:
|
||||||
nutritions[fn.nutrition_type.id] = c['amount'] / fn.food_amount * fn.nutrition_amount
|
nutritions[fn.nutrition_type.id] = c['amount'] / fn.food_amount * fn.nutrition_amount
|
||||||
|
|
||||||
return nutritions
|
return nutritions
|
||||||
|
|
||||||
@ -812,7 +817,8 @@ class RecipeSerializer(RecipeBaseSerializer):
|
|||||||
fields = (
|
fields = (
|
||||||
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time',
|
'id', 'name', 'description', 'image', 'keywords', 'steps', 'working_time',
|
||||||
'waiting_time', 'created_by', 'created_at', 'updated_at', 'source_url',
|
'waiting_time', 'created_by', 'created_at', 'updated_at', 'source_url',
|
||||||
'internal', 'show_ingredient_overview', 'nutrition', 'servings', 'file_path', 'servings_text', 'rating', 'last_cooked',
|
'internal', 'show_ingredient_overview', 'nutrition', 'servings', 'file_path', 'servings_text', 'rating',
|
||||||
|
'last_cooked',
|
||||||
'private', 'shared',
|
'private', 'shared',
|
||||||
)
|
)
|
||||||
read_only_fields = ['image', 'created_by', 'created_at']
|
read_only_fields = ['image', 'created_by', 'created_at']
|
||||||
@ -947,11 +953,11 @@ class ShoppingListRecipeSerializer(serializers.ModelSerializer):
|
|||||||
value = value.quantize(
|
value = value.quantize(
|
||||||
Decimal(1)) if value == value.to_integral() else value.normalize() # strips trailing zero
|
Decimal(1)) if value == value.to_integral() else value.normalize() # strips trailing zero
|
||||||
return (
|
return (
|
||||||
obj.name
|
obj.name
|
||||||
or getattr(obj.mealplan, 'title', None)
|
or getattr(obj.mealplan, 'title', None)
|
||||||
or (d := getattr(obj.mealplan, 'date', None)) and ': '.join([obj.mealplan.recipe.name, str(d)])
|
or (d := getattr(obj.mealplan, 'date', None)) and ': '.join([obj.mealplan.recipe.name, str(d)])
|
||||||
or obj.recipe.name
|
or obj.recipe.name
|
||||||
) + f' ({value:.2g})'
|
) + f' ({value:.2g})'
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
# TODO remove once old shopping list
|
# TODO remove once old shopping list
|
||||||
@ -1152,13 +1158,19 @@ class InviteLinkSerializer(WritableNestedModelSerializer):
|
|||||||
|
|
||||||
if obj.email:
|
if obj.email:
|
||||||
try:
|
try:
|
||||||
if InviteLink.objects.filter(space=self.context['request'].space, created_at__gte=datetime.now() - timedelta(hours=4)).count() < 20:
|
if InviteLink.objects.filter(space=self.context['request'].space,
|
||||||
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(self.context['request'].user.get_user_display_name())
|
created_at__gte=datetime.now() - timedelta(hours=4)).count() < 20:
|
||||||
message += _(' to join their Tandoor Recipes space ') + escape(self.context['request'].space.name) + '.\n\n'
|
message = _('Hello') + '!\n\n' + _('You have been invited by ') + escape(
|
||||||
message += _('Click the following link to activate your account: ') + self.context['request'].build_absolute_uri(reverse('view_invite', args=[str(obj.uuid)])) + '\n\n'
|
self.context['request'].user.get_user_display_name())
|
||||||
message += _('If the link does not work use the following code to manually join the space: ') + str(obj.uuid) + '\n\n'
|
message += _(' to join their Tandoor Recipes space ') + escape(
|
||||||
|
self.context['request'].space.name) + '.\n\n'
|
||||||
|
message += _('Click the following link to activate your account: ') + self.context[
|
||||||
|
'request'].build_absolute_uri(reverse('view_invite', args=[str(obj.uuid)])) + '\n\n'
|
||||||
|
message += _('If the link does not work use the following code to manually join the space: ') + str(
|
||||||
|
obj.uuid) + '\n\n'
|
||||||
message += _('The invitation is valid until ') + str(obj.valid_until) + '\n\n'
|
message += _('The invitation is valid until ') + str(obj.valid_until) + '\n\n'
|
||||||
message += _('Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub ') + 'https://github.com/vabene1111/recipes/'
|
message += _(
|
||||||
|
'Tandoor Recipes is an Open Source recipe manager. Check it out on GitHub ') + 'https://github.com/vabene1111/recipes/'
|
||||||
|
|
||||||
send_mail(
|
send_mail(
|
||||||
_('Tandoor Recipes Invite'),
|
_('Tandoor Recipes Invite'),
|
||||||
@ -1267,7 +1279,8 @@ class IngredientExportSerializer(WritableNestedModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Ingredient
|
model = Ingredient
|
||||||
fields = ('food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount', 'always_use_plural_unit', 'always_use_plural_food')
|
fields = ('food', 'unit', 'amount', 'note', 'order', 'is_header', 'no_amount', 'always_use_plural_unit',
|
||||||
|
'always_use_plural_food')
|
||||||
|
|
||||||
|
|
||||||
class StepExportSerializer(WritableNestedModelSerializer):
|
class StepExportSerializer(WritableNestedModelSerializer):
|
||||||
|
@ -805,7 +805,14 @@ class RecipeViewSet(viewsets.ModelViewSet):
|
|||||||
'steps__ingredients__food__substitute',
|
'steps__ingredients__food__substitute',
|
||||||
'steps__ingredients__food__child_inherit_fields',
|
'steps__ingredients__food__child_inherit_fields',
|
||||||
'steps__ingredients__food__foodnutrition_set',
|
'steps__ingredients__food__foodnutrition_set',
|
||||||
|
'steps__ingredients__food__foodnutrition_set__food',
|
||||||
|
'steps__ingredients__food__foodnutrition_set__food_unit',
|
||||||
|
'steps__ingredients__food__foodnutrition_set__nutrition_type',
|
||||||
'steps__ingredients__unit',
|
'steps__ingredients__unit',
|
||||||
|
'steps__ingredients__unit__unit_conversion_base_relation',
|
||||||
|
'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(
|
'cooklog_set').select_related(
|
||||||
'nutrition')
|
'nutrition')
|
||||||
|
Loading…
Reference in New Issue
Block a user