optimized unit conversion queries

using filter breaks prefetch related
This commit is contained in:
vabene1111 2023-02-26 08:22:07 +01:00
parent c217bf2445
commit 38010117e5
5 changed files with 113 additions and 45 deletions

View File

@ -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'),
),
]

View File

@ -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'),
),
]

View File

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

View File

@ -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 = (
'id', 'name', 'created_by', 'created_at', 'message', 'max_recipes', 'max_file_storage_mb', 'max_users',
'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb', 'allow_sharing', 'demo', 'food_inherit', 'show_facet_count', 'user_count', 'recipe_count', 'file_size_mb',
'image', 'use_plural',) 'image', 'use_plural',)
read_only_fields = ('id', 'created_by', 'created_at', 'max_recipes', 'max_file_storage_mb', 'max_users', 'allow_sharing', 'demo',) 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,8 +631,10 @@ 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()
for c in obj.unit.unit_conversion_base_relation.all():
if c.food is None or c.food == obj.food:
if obj.unit == c.base_unit: if obj.unit == c.base_unit:
conversions.append({ conversions.append({
'amount': obj.amount * (c.converted_amount / c.base_amount), 'amount': obj.amount * (c.converted_amount / c.base_amount),
@ -643,11 +650,9 @@ class IngredientSimpleSerializer(WritableNestedModelSerializer):
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:
@ -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']
@ -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):

View File

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