WIP
This commit is contained in:
@ -47,6 +47,67 @@ class TreeManager(MP_NodeManager):
|
|||||||
return self.model.add_root(**kwargs), True
|
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:
|
class PermissionModelMixin:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_space_key():
|
def get_space_key():
|
||||||
@ -275,7 +336,7 @@ class SyncLog(models.Model, PermissionModelMixin):
|
|||||||
return f"{self.created_at}:{self.sync} - {self.status}"
|
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
|
# TODO add find and fix problem functions
|
||||||
node_order_by = ['name']
|
node_order_by = ['name']
|
||||||
name = models.CharField(max_length=64)
|
name = models.CharField(max_length=64)
|
||||||
@ -283,63 +344,7 @@ class Keyword(ExportModelOperationsMixin('keyword'), MP_Node, PermissionModelMix
|
|||||||
description = models.TextField(default="", blank=True)
|
description = models.TextField(default="", blank=True)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
updated_at = models.DateTimeField(auto_now=True)
|
updated_at = models.DateTimeField(auto_now=True)
|
||||||
|
|
||||||
space = models.ForeignKey(Space, on_delete=models.CASCADE)
|
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:
|
class Meta:
|
||||||
constraints = [
|
constraints = [
|
||||||
@ -348,7 +353,7 @@ class Keyword(ExportModelOperationsMixin('keyword'), MP_Node, PermissionModelMix
|
|||||||
indexes = (Index(fields=['id', 'name']),)
|
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)])
|
name = models.CharField(max_length=128, validators=[MinLengthValidator(1)])
|
||||||
description = models.TextField(blank=True, null=True)
|
description = models.TextField(blank=True, null=True)
|
||||||
|
|
||||||
|
@ -211,9 +211,9 @@ class KeywordSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
|
|||||||
return str(obj)
|
return str(obj)
|
||||||
|
|
||||||
def get_image(self, obj):
|
def get_image(self, obj):
|
||||||
recipes = obj.recipe_set.all().filter(space=obj.space).exclude(image__isnull=True).exclude(image__exact='')
|
recipes = obj.recipe_set.all().exclude(image__isnull=True).exclude(image__exact='')
|
||||||
if len(recipes) == 0 and obj.has_children():
|
if len(recipes) == 0:
|
||||||
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 = 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:
|
if len(recipes) != 0:
|
||||||
return random.choice(recipes).image.url
|
return random.choice(recipes).image.url
|
||||||
else:
|
else:
|
||||||
@ -288,6 +288,26 @@ class SupermarketSerializer(UniqueFieldsMixin, SpacedModelSerializer):
|
|||||||
|
|
||||||
class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
||||||
supermarket_category = SupermarketCategorySerializer(allow_null=True, required=False)
|
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):
|
def create(self, validated_data):
|
||||||
validated_data['name'] = validated_data['name'].strip()
|
validated_data['name'] = validated_data['name'].strip()
|
||||||
@ -305,7 +325,7 @@ class FoodSerializer(UniqueFieldsMixin, WritableNestedModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Food
|
model = Food
|
||||||
fields = ('id', 'name', 'recipe', 'ignore_shopping', 'supermarket_category')
|
fields = ('id', 'name', 'recipe', 'ignore_shopping', 'supermarket_category', 'image')
|
||||||
|
|
||||||
|
|
||||||
class IngredientSerializer(WritableNestedModelSerializer):
|
class IngredientSerializer(WritableNestedModelSerializer):
|
||||||
|
Reference in New Issue
Block a user