Forráskód Böngészése

Merge branch 'feature/recipes' of 1ffy/sp-1 into master

Всеволод Левитан 1 éve
szülő
commit
cc3a7306de

+ 47 - 13
src/logic/start_factory.py

@@ -1,3 +1,5 @@
+from src.models.recipe_model import recipe_model
+from src.models.ingredient_model import ingredient_model
 from src.models.nomenclature_group_model import nomenclature_group_model
 from src.models.measurement_unit_model import measurement_unit_model
 from src.models.nomenclature_model import nomenclature_model
@@ -48,21 +50,38 @@ class start_factory:
         """
         return self.__storage
     
+    
     @staticmethod
-    def create_nomenclature_list():
+    def create_recipes():
         """
-          Фабричный метод Создать список номенклатуры
+            Фабричный метод Создать список рецептов
         """
-        
+
         result = []
-        
-        
-        item1 = nomenclature_model("Мука", "Мушка пшеничная", measurement_unit_model("грамм"), nomenclature_group_model("name"))
-        item1.group = nomenclature_group_model.create_group()
-        item1.unit = measurement_unit_model.create_kg()
-        
-        result.append(item1)
-        
+        g = measurement_unit_model.create_g()
+        pcs = measurement_unit_model.create_pcs()
+        group = nomenclature_group_model("group")
+
+        ingredients = [
+            ingredient_model("Мука", nomenclature_model("Мука", "Мука пшеничная", g, group), 100, g),
+            ingredient_model("Сахар", nomenclature_model("Сахар", "Сахарный песок", g, group), 80, g),
+            ingredient_model("Масло", nomenclature_model("Масло", "Масло сливочное", g, group), 70, g),
+            ingredient_model("Яйцо", nomenclature_model("Яйцо", "Яйцо куриное", pcs, group), 1, pcs),
+            ingredient_model("Ванилин", nomenclature_model("Ванилин", "Ванилин продуктовый", g, group), 5, g)
+        ]
+        steps = [
+            "Как испечь вафли хрустящие в вафельнице? Подготовьте необходимые продукты. Из данного количества у меня получилось 8 штук диаметром около 10 см.",
+            "Масло положите в сотейник с толстым дном. Растопите его на маленьком огне на плите, на водяной бане либо в микроволновке.",
+            "Добавьте в теплое масло сахар. Перемешайте венчиком до полного растворения сахара. От тепла сахар довольно быстро растает.",
+            "Добавьте в масло яйцо. Предварительно все-таки проверьте масло, не горячее ли оно, иначе яйцо может свариться. Перемешайте яйцо с маслом до однородности.",
+            "Всыпьте муку, добавьте ванилин.",
+            "Перемешайте массу венчиком до состояния гладкого однородного теста.",
+            "Разогрейте вафельницу по инструкции к ней. У меня очень старая, еще советских времен электровафельница. Она может и не очень красивая, но печет замечательно! Я не смазываю вафельницу маслом, в тесте достаточно жира, да и к ней уже давно ничего не прилипает. Но вы смотрите по своей модели. Выкладывайте тесто по столовой ложке. Можно класть немного меньше теста, тогда вафли будут меньше и их получится больше.",
+            "Пеките вафли несколько минут до золотистого цвета. Осторожно откройте вафельницу, она очень горячая! Снимите вафлю лопаткой. Горячая она очень мягкая, как блинчик. Но по мере остывания становится твердой и хрустящей. Такие вафли можно свернуть трубочкой. Но делать это надо сразу же после выпекания, пока она мягкая и горячая, потом у вас ничего не получится, вафля поломается. Приятного аппетита!"
+        ]
+
+        result.append(recipe_model.create("Вафли хрустящие в вафельнице", ingredients, steps))
+
         return result
     
     
@@ -79,8 +98,23 @@ class start_factory:
             self.__options.is_first_run = False
             
             # Формируем и зпоминаем номеклатуру
-            result = start_factory.create_nomenclature_list()
-            self.__save(storage.nomenclature_key(), result )
+            res = self.create_recipes()
+            ingredients = set()
+            for x in res:
+                for ing in x.ingredients:
+                    ingredients.add(ing)
+            recipes = res.copy()
+            nomenclatures = set()
+            for ingredient in ingredients:
+                nomenclatures.add(ingredient.nomenclature)
+            result = nomenclatures
+            munits = set([x.measurement_unit for x in nomenclatures])
+            nomgroups = set([x.nomenclature_group for x in nomenclatures])
+            self.__save(storage.nomenclature_key(), nomenclatures )
+            self.__save(storage.measurement_unit_key(), munits)
+            self.__save(storage.nomenclature_group_key(), nomgroups)
+            self.__save(storage.ingredient_key(), ingredients)
+            self.__save(storage.recipe_key(), recipes)
 
         return result
 

+ 91 - 0
src/models/ingredient_model.py

@@ -0,0 +1,91 @@
+from src.models.measurement_unit_model import measurement_unit_model
+from src.models.nomenclature_model import nomenclature_model
+from src.models.abstract_reference import abstract_reference
+from src.validation.validator import validator
+
+class ingredient_model (abstract_reference):
+    # Номенклатура ингредиента
+    __nomenclature = None
+
+    # Количество ингредиента в единицах измерения
+    __amount = 0
+
+    # Единица измерения количества ингредиента
+    __measurement_unit = None
+
+
+    # Валидатор
+    __vtor = validator()
+
+
+    def __init__(self, name, nomenclature:nomenclature_model, amount:int, unit:measurement_unit_model):
+        self.__vtor.check_type(nomenclature, nomenclature_model)
+        self.__vtor.check_number(amount)
+        self.__vtor.check_type(unit, measurement_unit_model)
+        self.__nomenclature = nomenclature
+        self.__amount = amount
+        self.__measurement_unit = unit
+        super().__init__(name)
+
+    
+    @property
+    def nomenclature(self):
+        """Номенклатура ингредиента"""
+
+        return self.__nomenclature
+    
+    @nomenclature.setter
+    def nomenclature(self, value: nomenclature_model):
+        """
+            Номенклатура ингредиента
+        Args:
+            value (nomenclature_model): номенклатура ингредиента
+        Raises:
+            argument_exception: Несоответствие типа аргумента
+        """
+
+        self.__vtor.check_type(value, nomenclature_model)
+
+        self.__nomenclature = value
+
+    
+    @property
+    def amount(self):
+        """Количество ингредиента"""
+
+        return self.__amount
+    
+    @amount.setter
+    def amount(self, value):
+        """
+            Количество ингредиента в его единице измерения
+        Args:
+            value (Number): количество ингредиента в его единице измерения
+        Raises:
+            argument_exception: Значение не является числом
+        """
+
+        self.__vtor.check_number(value)
+
+        self.__amount = value
+
+
+    @property
+    def measurement_unit(self):
+        """Единица измерения количества ингредиента"""
+
+        return self.__measurement_unit
+    
+    @measurement_unit.setter
+    def measurement_unit(self, value: measurement_unit_model):
+        """
+            Единица измерения количества ингредиента
+        Args:
+            value (measurement_unit_model): единица измерения количества ингредиента
+        Raises:
+            argument_exception: Несоответствие типа аргумента
+        """
+
+        self.__vtor.check_type(value, measurement_unit_model)
+
+        self.__measurement_unit = value

+ 73 - 48
src/models/recipe_model.py

@@ -1,60 +1,85 @@
+from src.models.ingredient_model import ingredient_model
 from src.models.abstract_reference import abstract_reference
-from src.models.nomenclature_model import nomenclature_model
-from src.models.measurement_unit_model import measurement_unit_model
-from src.errors.error_proxy import error_proxy
-
-#
-# Класс описание одной строки рецепта
-#
-class receipe_model(abstract_reference):
-    __nomenclature: nomenclature_model = None
-    __size: int = 0
-    __unit: measurement_unit_model = None
-    
-    def __init__(self, _nomenclature: nomenclature_model, _size: int, _unit: measurement_unit_model):
-        error_proxy.validate(_nomenclature, abstract_reference)
-        error_proxy.validate(_unit, abstract_reference)
-         
-        self.__nomenclature = _nomenclature
-        self.__size = _size
-        self.__unit = _unit
-        
-        super().__init__( f"{_nomenclature.name} , {_unit.name} ")
+from src.validation.validator import validator
+
+class recipe_model (abstract_reference):
+    # Ингредиенты рецепта
+    __ingredients = list()
+
+    # Шаги приготовления рецепта
+    __steps = list()
+
+
+    # Валидатор
+    __vtor = validator()
+
+
+    def __init__(self, name):
+        super().__init__(name)
+
     
-    @property
-    def nomenclature(self):
+    @staticmethod
+    def create(name, ingredients:list=None, steps:list=None):
         """
-            Номенклатура
+            Фабричный метод для создания рецепта
+        Args:
+            ingredients?: список ингредиентов
+            steps?: список шагов по приготовлению
         Returns:
-            _type_: _description_
+            recipe_model: Созданный рецепт
         """
-        return self.__nomenclature
-    
-    
+
+        recipe = recipe_model(name)
+        
+        recipe.ingredients = ingredients if ingredients is not None else list()
+        recipe.steps = steps if steps is not None else list()
+
+        return recipe
+
+
     @property
-    def size(self):
-        """
-            Размер
+    def ingredients(self):
+        """Ингредиенты рецепта"""
 
-        Returns:
-            _type_: _description_
-        """
-        return self.__size
-    
+        return self.__ingredients
     
-    @size.setter
-    def size(self, value: int):
-        self.__size = value
-    
-    
-    @property    
-    def unit(self):
+    @ingredients.setter
+    def ingredients(self, ingredients: list):
+        """
+            Ингредиенты рецепта
+        Args:
+            ingredients (list): ингредиенты рецепта
+        Raises:
+            argument_exception: Несоответствие типа аргумента
+            argument_exception: Один из членов коллекции не прошел валидацию
         """
-           Единица измерения
 
-        Returns:
-            _type_: _description_
+        self.__vtor.check_type(ingredients, list)
+        self.__vtor.check_collection_types_all(ingredients, ingredient_model)
+        
+        self.__ingredients = ingredients
+
+
+    @property
+    def steps(self):
+        """Шаги рецепта"""
+
+        return self.__steps
+    
+    @steps.setter
+    def steps(self, steps: list):
         """
-        return self.__unit    
+            Шаги рецепта
+        Args:
+            steps (list): шаги рецепта
+        Raises:
+            argument_exception: Несоответствие типа аргумента
+            argument_exception: Один из членов коллекции не прошел валидацию
+        """
+
+        self.__vtor.check_type(steps, list)
+        self.__vtor.check_collection_all(steps,
+                                               lambda item: self.__vtor.check_type(item, str))
+        self.__vtor.check_collection_types_all(steps, str)
         
-    
+        self.__steps = steps

+ 22 - 3
src/storage/storage.py

@@ -32,7 +32,7 @@ class storage:
 
   
     @staticmethod
-    def group_key():
+    def nomenclature_group_key():
         """
             Списк номенклатурных групп
         Returns:
@@ -41,13 +41,32 @@ class storage:
         return "group"
       
       
-
     @staticmethod  
-    def unit_key():
+    def measurement_unit_key():
         """
               Список единиц измерения
         Returns:
             _type_: _description_
         """
         return "unit"
+    
+
+    @staticmethod
+    def ingredient_key():
+        """
+            Список ингредиентов
+        Returns:
+            _type_: _description_
+        """
+        return "ingredient"
+    
+
+    @staticmethod
+    def recipe_key():
+        """
+            Список рецептов
+        Returns:
+            _type_: _description_
+        """
+        return "recipe"
     

+ 57 - 3
src/validation/validator.py

@@ -8,13 +8,28 @@ class validator:
         if not hasattr(cls, "instance"):
             cls.instance = super(validator, cls).__new__(cls)
         return cls.instance
+    
+    def check(self, value, method):
+        """
+            Валидация аргумента по лямбде
+        Args:
+            value (any): Передаваемый аргумент
+            method (lambda): Лямбда-функция для проверки
+        Raises:
+            argument_exception: Аргумент не прошел валидацию по лямбде
+        """
+
+        if not method(value):
+            raise argument_exception("Аргумент не прошел валидацию по лямбде")
+        
+        return True
 
     def check_type(self, value, exp_type):
         """
             Валидация аргумента по типу
         Args:
             value (any): Передаваемый аргумент
-            type(Type): Ожидаемый тип
+            type (Type): Ожидаемый тип
         Raises:
             argument_exception: Некорректный тип аргумента
         """
@@ -161,7 +176,7 @@ class validator:
         """
             Валидация аргумента по соответствию типу Число
         Args:
-            value: Передаваемый аргумент
+            value (any): Передаваемый аргумент
         Raises:
             argument_exception: Аргумент не является числом
         """
@@ -169,4 +184,43 @@ class validator:
         if not isinstance(value, Number):
             raise argument_exception(f"Аргумент не является числом (type={type(value)})")
         
-        return True
+        return True
+    
+    def check_collection_all(self, value, method):
+        """
+            Валидация коллекции по соответствию лямбде
+        Args:
+            value (any): Передаваемая коллекция
+            method (lambda): Лямбда-функция валидации
+        Raises:
+            argument_exception: Один из членов коллекции не прошел валидацию
+        """
+
+        for num, val in enumerate(value):
+            try:
+                if not method(val):
+                    raise argument_exception("Член коллекции не прошел валидацию")
+            except argument_exception as exc:
+                raise argument_exception(f"Один из членов коллекции не прошел валидацию \
+                                         ({exc.error.error_text} on item #{num})")
+            
+        return True
+    
+    def check_collection_types_all(self, value, exp_type):
+        """
+            Валидация коллекции по соответствию типа
+        Args:
+            value (any): Передаваемая коллекция
+            type (Type): Ожидаемый тип элементов
+        Raises:
+            argument_exception: Один из членов коллекции не соответствует типу
+        """
+
+        for num, val in enumerate(value):
+            try:
+                self.check_type(val, exp_type)
+            except argument_exception as exc:
+                raise argument_exception(f"Один из членов коллекции не соответствует типу {exp_type} \
+                                         (excepted {exp_type}, got {type(val)} on element #{num})")
+            
+        return True

+ 27 - 6
tests/test_factory.py

@@ -1,6 +1,7 @@
 from src.models.measurement_unit_model import measurement_unit_model
 from src.logic.start_factory import start_factory
 from src.settings.settings_manager import settings_manager
+import os
 
 import unittest
 
@@ -14,7 +15,7 @@ class factory_test(unittest.TestCase):
     #
     # Проверка создания ед. измерения
     #
-    def test_check_factory(self):
+    def test_factory(self):
         # Подготовка
         unit = measurement_unit_model.create_kg()
         
@@ -27,20 +28,41 @@ class factory_test(unittest.TestCase):
     # 
     # Проверка создание начальной номенклатуры
     #    
-    def test_check_create_nomenclature(self):
+    def test_create_recipes(self):
         # Подготовка
-        items = start_factory.create_nomenclature_list()
+        items = start_factory.create_recipes()
         
         # действие
         
         # Прверки
         assert len(items) > 0 
         
+    #
+    # Проверка создания и записи списка номенклатур
+    #
+    def test_create(self):
+        # Подготовка
+        stor = storage()
+
+        setman = settings_manager()
+        setman.open(f"{os.path.dirname(__file__) + '/..'}/config/settings.json")
+        st = setman.settings
+
+        start_factory(st, stor).create()
         
+        # Действие
+
+        # Проверка
+        assert len(stor.data[stor.nomenclature_key()]) > 0
+        assert len(stor.data[stor.measurement_unit_key()]) > 0
+        assert len(stor.data[stor.nomenclature_group_key()]) > 0
+        assert len(stor.data[stor.ingredient_key()]) > 0
+        assert len(stor.data[stor.recipe_key()]) > 0
+
     #      
     # Проверка работы класса start_factory
     #
-    def test_check_start_factor(self):
+    def test_start_factory(self):
         # Подготовка
         manager = settings_manager()
         strg = storage()
@@ -58,5 +80,4 @@ class factory_test(unittest.TestCase):
         
         
         assert len(result) == 0    
-        
-       
+        

+ 41 - 0
tests/test_models.py

@@ -1,4 +1,6 @@
 import unittest
+from src.models.ingredient_model import ingredient_model
+from src.models.recipe_model import recipe_model
 from src.models.company_model import company_model
 from src.settings.settings_manager import settings_manager
 from src.models.measurement_unit_model import measurement_unit_model
@@ -55,6 +57,45 @@ class test_models(unittest.TestCase):
         # Проверка
         assert isinstance(nom, nomenclature_model)
         assert nom.full_name == fn
+
+    def test_ingredient(self):
+        # Подготовка
+        munit = measurement_unit_model.create_g()
+        nom = nomenclature_model("A", "A", munit, nomenclature_group_model("group"))
+        amount = 10
+
+        ingredient = ingredient_model("Тест", nom, amount, munit)
+
+        # Действие
+
+        # Проверка
+        assert isinstance(ingredient, ingredient_model)
+        assert ingredient.amount == amount
+        assert ingredient.nomenclature == nom
+        assert ingredient.measurement_unit == munit
+
+    def test_recipe(self):
+        # Подготовка
+        munit = measurement_unit_model.create_g()
+        nom = nomenclature_model("A", "A", munit, nomenclature_group_model("group"))
+        nom2 = nomenclature_model("B", "B", munit, nomenclature_group_model("group"))
+        amount = 10
+        ingredient = ingredient_model("Тест", nom, amount, munit)
+        ingredient2 = ingredient_model("Тест", nom2, amount, munit)
+        steps = ["Aaaa.", "Bbbb."]
+
+        recipe = recipe_model.create("Тест", [ingredient, ingredient2], steps)
+
+        # Действие
+
+        # Проверка
+        assert isinstance(recipe, recipe_model)
+        assert len(recipe.ingredients) == 2
+        assert recipe.ingredients[0] == ingredient
+        assert recipe.ingredients[1] == ingredient2
+        assert len(recipe.steps) == 2
+        assert recipe.steps[0] == steps[0]
+        assert recipe.steps[1] == steps[1]
         
     def test_base_name_validation(self):
         # Подготовка

+ 43 - 6
tests/test_validator.py

@@ -4,8 +4,19 @@ import unittest
 
 class test_validator(unittest.TestCase):
 
+    def test_validator_lambda(self):
+        """Проверить работоспособность validator.check()"""
+        # Подготовка
+        vtor = validator()
+
+        # Действие
+
+        # Проверка
+        assert vtor.check(10, lambda x: x > 9)
+        self.assertRaises(argument_exception, lambda: vtor.check(10, lambda x: x < 9))
+
     def test_validator_typeguard(self):
-        """Проверить работоспособность settings_validation.check_type()"""
+        """Проверить работоспособность validator.check_type()"""
         # Подготовка
         vtor = validator()
 
@@ -16,7 +27,7 @@ class test_validator(unittest.TestCase):
         self.assertRaises(argument_exception, lambda: vtor.check_type("", int))
 
     def test_validator_len_exact(self):
-        """Проверить работоспособность settings_validator.check_length()"""
+        """Проверить работоспособность validator.check_length()"""
         # Подготовка
         vtor = validator()
 
@@ -28,7 +39,7 @@ class test_validator(unittest.TestCase):
         self.assertRaises(argument_exception, lambda: vtor.check_length("aa", 3))
 
     def test_validator_len_int(self):
-        """Проверить работоспособность settings_validator.check_length() при передаче int"""
+        """Проверить работоспособность validator.check_length() при передаче int"""
         # Подготовка
         vtor = validator()
 
@@ -40,7 +51,7 @@ class test_validator(unittest.TestCase):
         self.assertRaises(argument_exception, lambda: vtor.check_length(10, 3))
 
     def test_validator_len_bound(self):
-        """Проверить работоспособность settings_validator.check_length_bound()"""
+        """Проверить работоспособность validator.check_length_bound()"""
         # Подготовка
         vtor = validator()
 
@@ -62,7 +73,7 @@ class test_validator(unittest.TestCase):
         self.assertRaises(argument_exception, lambda: vtor.check_length_bound("abc", 1, 3, inclusive_max=False))
 
     def test_validator_regex(self):
-        """Проверить работоспособность settings_validator.check_regex()"""
+        """Проверить работоспособность validator.check_regex()"""
         # Подготовка
         vtor = validator()
 
@@ -70,4 +81,30 @@ class test_validator(unittest.TestCase):
 
         # Проверка
         assert vtor.check_regex("abc", ".*b[c]+")
-        self.assertRaises(argument_exception, lambda: vtor.check_regex("abc", ".*b[c]+[a]+"))
+        self.assertRaises(argument_exception, lambda: vtor.check_regex("abc", ".*b[c]+[a]+"))
+
+    def test_validator_collection_all(self):
+        """Проверить работоспособность validator.check_collection_all()"""
+        # Подготовка
+        vtor = validator()
+        col1 = [5, 6, 7]
+        col2 = [4, 5, 6]
+
+        # Действие
+
+        # Проверка
+        assert vtor.check_collection_all(col1, lambda x: x > 4)
+        self.assertRaises(argument_exception, lambda: vtor.check_collection_all(col2, lambda x: x > 4))
+
+    def test_validator_collection_types_all(self):
+        """Проверить работоспособность validator.check_collection_types_all()"""
+        # Подготовка
+        vtor = validator()
+        col1 = ["a", "b", "c"]
+        col2 = ["a", 2, None]
+
+        # Действие
+
+        # Проверка
+        assert vtor.check_collection_types_all(col1, str)
+        self.assertRaises(argument_exception, lambda: vtor.check_collection_types_all(col2, str))