diff --git a/cookbook/models.py b/cookbook/models.py index 5b74c8610..7913678b0 100644 --- a/cookbook/models.py +++ b/cookbook/models.py @@ -47,6 +47,67 @@ class TreeManager(MP_NodeManager): return self.model.add_root(**kwargs), True +class TreeModel(MP_Node): + objects = ScopedManager(space='space', _manager_class=TreeManager) + + _full_name_separator = ' > ' + + def __str__(self): + if self.icon: + return f"{self.icon} {self.name}" + else: + return f"{self.name}" + + @property + def parent(self): + parent = self.get_parent() + if parent: + return self.get_parent().id + return None + + @property + def full_name(self): + """ + Returns a string representation of a tree node and it's ancestors, + e.g. 'Cuisine > Asian > Chinese > Catonese'. + """ + names = [node.name for node in self.get_ancestors_and_self()] + return self._full_name_separator.join(names) + + def get_ancestors_and_self(self): + """ + Gets ancestors and includes itself. Use treebeard's get_ancestors + if you don't want to include the node itself. It's a separate + function as it's commonly used in templates. + """ + if self.is_root(): + return [self] + return list(self.get_ancestors()) + [self] + + def get_descendants_and_self(self): + """ + Gets descendants and includes itself. Use treebeard's get_descendants + if you don't want to include the node itself. It's a separate + function as it's commonly used in templates. + """ + return self.get_tree(self) + + def has_children(self): + return self.get_num_children() > 0 + + def get_num_children(self): + return self.get_children().count() + + # use self.objects.get_or_create() instead + @classmethod + def add_root(self, **kwargs): + with scopes_disabled(): + return super().add_root(**kwargs) + + class Meta: + abstract = True + + class PermissionModelMixin: @staticmethod def get_space_key(): @@ -275,7 +336,7 @@ class SyncLog(models.Model, PermissionModelMixin): return f"{self.created_at}:{self.sync} - {self.status}" -class Keyword(ExportModelOperationsMixin('keyword'), MP_Node, PermissionModelMixin): +class Keyword(ExportModelOperationsMixin('keyword'), TreeModel, PermissionModelMixin): # TODO add find and fix problem functions node_order_by = ['name'] name = models.CharField(max_length=64) @@ -283,63 +344,7 @@ class Keyword(ExportModelOperationsMixin('keyword'), MP_Node, PermissionModelMix description = models.TextField(default="", blank=True) created_at = models.DateTimeField(auto_now_add=True) updated_at = models.DateTimeField(auto_now=True) - space = models.ForeignKey(Space, on_delete=models.CASCADE) - objects = ScopedManager(space='space', _manager_class=TreeManager) - - _full_name_separator = ' > ' - - def __str__(self): - if self.icon: - return f"{self.icon} {self.name}" - else: - return f"{self.name}" - - @property - def parent(self): - parent = self.get_parent() - if parent: - return self.get_parent().id - return None - - @property - def full_name(self): - """ - Returns a string representation of the keyword and it's ancestors, - e.g. 'Cuisine > Asian > Chinese > Catonese'. - """ - names = [keyword.name for keyword in self.get_ancestors_and_self()] - return self._full_name_separator.join(names) - - def get_ancestors_and_self(self): - """ - Gets ancestors and includes itself. Use treebeard's get_ancestors - if you don't want to include the keyword itself. It's a separate - function as it's commonly used in templates. - """ - if self.is_root(): - return [self] - return list(self.get_ancestors()) + [self] - - def get_descendants_and_self(self): - """ - Gets descendants and includes itself. Use treebeard's get_descendants - if you don't want to include the keyword itself. It's a separate - function as it's commonly used in templates. - """ - return self.get_tree(self) - - def has_children(self): - return self.get_num_children() > 0 - - def get_num_children(self): - return self.get_children().count() - - # use self.objects.get_or_create() instead - @classmethod - def add_root(self, **kwargs): - with scopes_disabled(): - return super().add_root(**kwargs) class Meta: constraints = [ @@ -348,7 +353,7 @@ class Keyword(ExportModelOperationsMixin('keyword'), MP_Node, PermissionModelMix indexes = (Index(fields=['id', 'name']),) -class Unit(ExportModelOperationsMixin('unit'), models.Model, PermissionModelMixin): +class Unit(ExportModelOperationsMixin('unit'), TreeModel, PermissionModelMixin): name = models.CharField(max_length=128, validators=[MinLengthValidator(1)]) description = models.TextField(blank=True, null=True) diff --git a/cookbook/serializer.py b/cookbook/serializer.py index 8cba8be5c..a8bfc90e7 100644 --- a/cookbook/serializer.py +++ b/cookbook/serializer.py @@ -211,9 +211,9 @@ class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer): return str(obj) def get_image(self, obj): - recipes = obj.recipe_set.all().filter(space=obj.space).exclude(image__isnull=True).exclude(image__exact='') - if len(recipes) == 0 and obj.has_children(): - recipes = Recipe.objects.filter(keywords__in=obj.get_descendants(), space=obj.space).exclude(image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree + recipes = obj.recipe_set.all().exclude(image__isnull=True).exclude(image__exact='') + if len(recipes) == 0: + recipes = Recipe.objects.filter(keywords__in=obj.get_tree()).exclude(image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree if len(recipes) != 0: return random.choice(recipes).image.url else: @@ -288,6 +288,26 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer): class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer): supermarket_category = SupermarketCategorySerializer(allow_null=True, required=False) + image = serializers.SerializerMethodField('get_image') + #numrecipe = serializers.SerializerMethodField('count_recipes') + + # TODO check if it is a recipe and get that image first + def get_image(self, obj): + if obj.recipe: + recipes = Recipe.objects.filter(id=obj.recipe).exclude(image__isnull=True).exclude(image__exact='') + if len(recipes) == 0: + return recipes.image.url + recipes = Recipe.objects.filter(steps__ingredients__food=obj).exclude(image__isnull=True).exclude(image__exact='') + if len(recipes) == 0: + recipes = Recipe.objects.filter(keywords__in=obj.get_tree()).exclude(image__isnull=True).exclude(image__exact='') # if no recipes found - check whole tree + # if len(recipes) != 0: + # return random.choice(recipes).image.url + # else: + # return None + return None + + # def count_recipes(self, obj): + # return obj.recipe_set.all().count() def create(self, validated_data): validated_data['name'] = validated_data['name'].strip() @@ -305,7 +325,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer): class Meta: model = Food - fields = ('id', 'name', 'recipe', 'ignore_shopping', 'supermarket_category') + fields = ('id', 'name', 'recipe', 'ignore_shopping', 'supermarket_category', 'image') class IngredientSerializer(WritableNestedModelSerializer):