diff --git a/AUTHORS.rst b/AUTHORS.rst index f84c29ee0..6c3e01084 100644 --- a/AUTHORS.rst +++ b/AUTHORS.rst @@ -64,6 +64,7 @@ Developers * absolutely-not-bot - https://github.com/absolutely-not-bot * Jaspreet Dhillon - https://github.com/jaspreetsd902 * Sofiko Alaverdashvili - https://github.com/sophiamartelli +* Oebele Lijzenga - https://github.com/olijzenga Translators ----------- diff --git a/wger/nutrition/api/views.py b/wger/nutrition/api/views.py index 7eb0b3693..3be9efbe9 100644 --- a/wger/nutrition/api/views.py +++ b/wger/nutrition/api/views.py @@ -22,6 +22,8 @@ from django.shortcuts import get_object_or_404 # Third Party +from easy_thumbnails.alias import aliases +from easy_thumbnails.files import get_thumbnailer from rest_framework import viewsets from rest_framework.decorators import ( action, @@ -167,11 +169,22 @@ def search(request): ) for ingredient in ingredients: + if hasattr(ingredient, 'image'): + image_obj = ingredient.image + image = image_obj.image.url + t = get_thumbnailer(image_obj.image) + thumbnail = t.get_thumbnail(aliases.get('micro_cropped')).url + else: + image = None + thumbnail = None + ingredient_json = { 'value': ingredient.name, 'data': { 'id': ingredient.id, 'name': ingredient.name, + 'image': image, + 'image_thumbnail': thumbnail } } results.append(ingredient_json) diff --git a/wger/nutrition/management/commands/download-off-ingredient-images.py b/wger/nutrition/management/commands/download-off-ingredient-images.py new file mode 100644 index 000000000..a58b4273d --- /dev/null +++ b/wger/nutrition/management/commands/download-off-ingredient-images.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 *-* + +# This file is part of wger Workout Manager. +# +# wger Workout Manager is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# wger Workout Manager is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU Affero General Public License + +# Standard Library +import datetime + +# Django +from django.conf import settings +from django.contrib.sites.models import Site +from django.core import mail +from django.core.management.base import BaseCommand +from django.template import loader +from django.utils import translation +from django.utils.translation import gettext_lazy as _ + +# wger +from wger.nutrition.models import MealItem + + +class Command(BaseCommand): + """ + Download images of all Open Food Facts ingredients that are used in a nutrition plan + """ + + help = 'Download images of all Open Food Facts ingredients that are used in a nutrition plan' + + def handle(self, **options): + # Make sure off downloads are enabled + settings.WGER_SETTINGS['DOWNLOAD_FROM_OFF'] = True + + # Since each MealItem is linked to a NutritionPlan via a Meal we can skip accessing + # NutritionPlan and Meal itself and fetch all MealItems directly instead. + meal_items = MealItem.objects.all() + meal_item_counter = 0 + download_counter = 0 + for meal_item in meal_items: + if meal_item.ingredient.fetch_image(): + download_counter += 1 + meal_item_counter += 1 + + if meal_item_counter % 10 == 0: + self.stdout.write( + f'Processed {meal_item_counter} meal items, ' + f'downloaded {download_counter} images' + ) + + + self.stdout.write( + f'Processed {meal_item_counter} meal items, downloaded {download_counter} images' + ) + self.stdout.write(f'Done') diff --git a/wger/nutrition/migrations/0011_merge_20220406_2021.py b/wger/nutrition/migrations/0011_merge_20220406_2021.py new file mode 100644 index 000000000..a3ebf1c74 --- /dev/null +++ b/wger/nutrition/migrations/0011_merge_20220406_2021.py @@ -0,0 +1,14 @@ +# Generated by Django 3.2.12 on 2022-04-06 18:21 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('nutrition', '0010_logitem_meal'), + ('nutrition', '0010_merge_0009_auto_20210802_1526_0009_meal_name'), + ] + + operations = [ + ] diff --git a/wger/nutrition/models/ingredient.py b/wger/nutrition/models/ingredient.py index fedaedcfa..d8251156a 100644 --- a/wger/nutrition/models/ingredient.py +++ b/wger/nutrition/models/ingredient.py @@ -446,18 +446,13 @@ def off_link(self): if self.source_name == Source.OPEN_FOOD_FACTS.value: return f'https://world.openfoodfacts.org/product/{self.code}/' - def get_image(self, request: HttpRequest): + def fetch_image(self): """ - Returns the ingredient image + Fetches the ingredient image from Open Food Facts servers if it is not available locally - If it is not available locally, it is fetched from Open Food Facts servers + Returns the image if it was fetched """ - try: - return self.image - except Ingredient.image.RelatedObjectDoesNotExist: - pass - - if not request.user.is_authenticated: + if hasattr(self, 'image'): return if self.source_name != Source.OPEN_FOOD_FACTS.value: @@ -507,3 +502,17 @@ def get_image(self, request: HttpRequest): 'size': len(downloaded_image.content) } return Image.from_json(self, downloaded_image, image_data, headers, generate_uuid=True) + + def get_image(self, request: HttpRequest): + """ + Returns the ingredient image + + If it is not available locally, it is fetched from Open Food Facts servers + """ + if hasattr(self, 'image'): + return self.image + + if not request.user.is_authenticated: + return + + return self.fetch_image() diff --git a/wger/nutrition/tests/test_ingredient.py b/wger/nutrition/tests/test_ingredient.py index 22c71f9d6..3f8700db2 100644 --- a/wger/nutrition/tests/test_ingredient.py +++ b/wger/nutrition/tests/test_ingredient.py @@ -246,7 +246,16 @@ def search_ingredient(self, fail=True): result = json.loads(response.content.decode('utf8')) self.assertEqual(len(result['suggestions']), 2) self.assertEqual(result['suggestions'][0]['value'], 'Ingredient, test, 2, organic, raw') + self.assertEqual(result['suggestions'][0]['data']['id'], 2) + suggestion_0_name = 'Ingredient, test, 2, organic, raw' + self.assertEqual(result['suggestions'][0]['data']['name'], suggestion_0_name) + self.assertEqual(result['suggestions'][0]['data']['image'], None) + self.assertEqual(result['suggestions'][0]['data']['image_thumbnail'], None) self.assertEqual(result['suggestions'][1]['value'], 'Test ingredient 1') + self.assertEqual(result['suggestions'][1]['data']['id'], 1) + self.assertEqual(result['suggestions'][1]['data']['name'], 'Test ingredient 1') + self.assertEqual(result['suggestions'][1]['data']['image'], None) + self.assertEqual(result['suggestions'][1]['data']['image_thumbnail'], None) # Search for an ingredient pending review (0 hits, "Pending ingredient") response = self.client.get(reverse('ingredient-search'), {'term': 'Pending'}, **kwargs)