Skip to content
  • Jon Badiali's avatar
    [YottaDB/DB/YDBDoc#355] Created basic web application using Python wrapper (YDBApron) · 2d9198e1
    Jon Badiali authored
    YottaDB/DB/YDBDoc#355 requests the creation of an Applications Manual demonstrating how to use YottaDB to build applications in a variety of domains. One of these domains is web application development.
    
    To satisfy the request for a web application using YottaDB, this commit implements a simple recipe application named YDBApron using the YDBPython for database operations and the Flask web development framework for application logic.
    
    Since YDBApron is a fully functional, self-contained application, there are no discrete changes to log in this commit.
    
    So, instead of going through line-by-line changes, this commit message simply provides an overview of the application itself as well as its data model.
    
    YDBApron is a simple [Model-View-Controller (MVC)](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller) application that uses a conventional Flask application structure:
    
    ```
        ├── COPYING
        ├── LICENSE
        ├── README.md
        ├── YDBApron
        │   ├── __init__.py
        │   ├── forms.py
        │   ├── globals.py
        │   ├── ingredients.py
        │   ├── recipes.py
        │   ├── schedules.py
        │   └── templates
        │       ├── add_ingredient.html
        │       ├── add_recipe.html
        │       ├── add_schedule.html
        │       ├── base.html
        │       ├── index.html
        │       ├── ingredients.html
        │       ├── recipe.html
        │       ├── recipes.html
        │       ├── schedule.html
        │       └── schedules.html
        ├── setup.py
        └── tests
            └── testdata.zwr
    ```
    
    Here there is a `YDBApron` directory under the top-level project directory. This directory represents the YDBApron Python module and contains all the application logic and HTML templates.
    
    Specifically, the `YDBApron` directory contains the following files:
    
    * `__init__.py`: Contains logic to create and initialize a Flask application.
    * `forms.py`: Contains class definitions for various web forms
    * `globals.py`: Contains definitions for global variables used in other modules
    * `ingredients.py`: Contains logic for adding, editing, and deleting ingredient information from the database.
    * `recipes.py`: Contains logic for adding, editing, and deleting recipe information from the database.
    * `schedules.py`: Contains logic for adding, editing, and deleting production schedule information from the database.
    
    Together, these files represent the *controller* portion of this application.
    
    Additionally, the `YDBApron` directory contains a `templates` directory with all the Jinja2 HTML templates used to generate the application web interface. This directory contains the following files:
    
    * `add_ingredient.html`: Template logic to display a form for adding and editing ingredients
    * `add_recipe.html`: Template logic to display a form for adding and editing recipes
    * `add_schedule.html`: Template logic to display a form for adding and editing production schedules
    * `base.html`: Template logic used across all other template pages
    * `index.html`: Homepage template
    * `ingredients.html`: Ingredient list page template
    * `recipe.html`: Individual recipe page template
    * `recipes.html`: Recipe list page template
    * `schedule.html`: Individual schedule page template
    * `schedules.html`: Schedule list page template
    
    These template represent the application *views* on the database, and so rely on the data structures and functions defined in the application controller files listed above.
    
    The application data model itself is discussed in the following section.
    
    The `setup.py` file contains basic application setup logic required for running YDBApron as a Python module.
    
    The `tests` directory contains sample application data in `testdata.zwr`, as well as some simple unit testing infrastructure.
    
    The YDBApron data model combines the hierarchical key-value structure of YottaDB with a conventional relational model.
    
    Specifically, YDBApron uses the native YottaDB tree model to categorize various data object types, e.g. recipes, ingredients, etc. Actual data objects are then stored in a relational format as YottaDB node values. For example, consider the YDB database nodes contained in `testdata.zwr`:
    
    ```
      ^YDBApron("recipes","breads","German Rye","ingredients","flour","rye flour")="500|g|0.581"
      ^YDBApron("recipes","breads","German Rye","ingredients","wet","water")="350|g|0.407"
      ^YDBApron("recipes","breads","German Rye","ingredients","dry","salt")="10|g|0.012"
      ^YDBApron("recipes","breads","German Rye","specifications","loaf","large")="800|g"
      ^YDBApron("recipes","breads","German Rye","specifications","loaf","small")="300|g"
      ^YDBApron("recipes","breads","Sourdough","ingredients","flour","wheat flour")="500|g|0.602"
      ^YDBApron("recipes","breads","Sourdough","ingredients","wet","water")="320|g|0.386"
      ^YDBApron("recipes","breads","Sourdough","ingredients","dry","salt")="10|g|0.012"
      ^YDBApron("recipes","breads","Sourdough","specifications","loaf","large")="800|g"
      ^YDBApron("recipes","breads","Sourdough","specifications","loaf","small")="300|g"
      ^YDBApron("ingredients","flour","rye flour")="500|g|3.5|Bob's Red Mill|Shoprite"
      ^YDBApron("ingredients","flour","wheat flour")="500|g|2.5|King Arthur|Shoprite"
      ^YDBApron("ingredients","wet","water")="100|g|0|N/A|N/A"
      ^YDBApron("ingredients","dry","salt")="200|g|1.5|Morton's|Shoprite"
      ^YDBApron("schedules","Wednesday","breads","German Rye","specifications","loaf","large")=2
      ^YDBApron("schedules","Wednesday","breads","German Rye","specifications","loaf","small")=4
      ^YDBApron("schedules","Wednesday","breads","German Rye","supplementary")=200
      ^YDBApron("schedules","Wednesday","breads","Sourdough","specifications","loaf","large")=2
      ^YDBApron("schedules","Wednesday","breads","Sourdough","specifications","loaf","small")=4
      ^YDBApron("schedules","Wednesday","breads","Sourdough","supplementary")=200
    ```
    
    These nodes collectively represent a combination of recipes, ingredients, and production schedules. Specifically, the following items are represented:
    
    * Two recipes: "German Rye" and "Sourdough"
    * Four ingredients: "rye flour", "wheat flour", "water", and "salt"
    * One product schedule: "Wednesday"
    
    Each recipe is stored across multiple nodes representing a combination of ingredient and product specification information. Each ingredient gets its own node in the sub-tree for the given recipe. Each node is referenced using a series of keys of the following format:
    
    ```
      ^YDBApron("recipes",recipe_category,recipe_name,"ingredients",ingredient_category,ingredient_name)="weight|weight_unit|proportion"
    ```
    
    For example:
    
    ```
      ^YDBApron("recipes","breads","German Rye","ingredients","flour","rye flour")="500|g|0.581"
      ^YDBApron("recipes","breads","German Rye","ingredients","wet","water")="350|g|0.407"
      ^YDBApron("recipes","breads","German Rye","ingredients","dry","salt")="10|g|0.012"
    ```
    
    Together, these nodes contain all recipe details for the following ingredients of the "German Rye" recipe: rye flour, water, and salt.
    
    Note in particular that both recipe and ingredient information is stored in these keys. Each node value itself contains the following information about an ingredient in the "German Rye" recipe:
    
    * Total weight
    * Weight unit, e.g. grams
    * Proportion of total recipe yield
    
    This information is stored in each node as pipe-delimited list that can be interpreted using relational data model with the `YottaDB Octo® <https://docs.yottadb.com/Octo/index.html>`_ SQL engine. How to use Octo for relational data access is, however, beyond the scope of this tutorial.
    
    Similarly, each product specification is stored using the following key layout:
    
    ```
      ^YDBApron("recipes",recipe_category,recipe_name,"specifications",specification_format,specification_size)="weight|weight_unit"
    ```
    
    For example:
    
    ```
      ^YDBApron("recipes","breads","German Rye","specifications","loaf","large")="800|g"
      ^YDBApron("recipes","breads","German Rye","specifications","loaf","small")="300|g"
    ```
    
    Each product specification node value contains the following information about a product specification defined for the "German Rye" recipe:
    
    * Total weight
    * Weight unit
    
    Ingredients are stored in YDBApron using the following key-value layout:
    
    ```
      ^YDBApron("ingredients",ingredient_category,ingredient_name)="ingredient_weight|weight_unit|price|manufacturer|vendor"
    ```
    
    For example:
    
    ```
      ^YDBApron("ingredients","flour","rye flour")="500|g|3.5|Bob's Red Mill|Shoprite"
      ^YDBApron("ingredients","flour","wheat flour")="500|g|2.5|King Arthur|Shoprite"
      ^YDBApron("ingredients","wet","water")="100|g|0|N/A|N/A"
      ^YDBApron("ingredients","dry","salt")="200|g|1.5|Morton's|Shoprite"
    ```
    
    Production schedules are stored in YDBApron using the following key-value layout:
    
    ```
      ^YDBApron("schedules",schedule_name,recipe_category,recipe_name,"specifications",specification_format,specification_size)=specification_volume
      ^YDBApron("schedules",schedule_name,recipe_category,recipe_name,"supplementary")=supplementary_yield
    ```
    
    For example:
    
    ```
      ^YDBApron("schedules","Wednesday","breads","German Rye","specifications","loaf","large")=2
      ^YDBApron("schedules","Wednesday","breads","German Rye","specifications","loaf","small")=4
      ^YDBApron("schedules","Wednesday","breads","German Rye","supplementary")=200
      ^YDBApron("schedules","Wednesday","breads","Sourdough","specifications","loaf","large")=2
      ^YDBApron("schedules","Wednesday","breads","Sourdough","specifications","loaf","small")=4
      ^YDBApron("schedules","Wednesday","breads","Sourdough","supplementary")=200
    ```
    
    In each case, the `specification_volume` is the total number of the given specification for the given recipe to be produced. For example, the "Wednesday" schedule calls for 2 large loaves of "German Rye" to be produced and 4 small loaves of "Sourdough" to be produced.
    
    Additionally, each schedule allows for a supplementary yield to be specified for each recipe. This allows users to schedule recipe production that is not allocated to any particular product format.
    
    For example, the "Wednesday" schedule specificies 200g of supplementary yield for the "Sourdough" recipe. So, in addition to the 2 large loaves and 4 small loaves scheduled to be produced, an additional 200g of "Sourdough" dough will also be produced.
    2d9198e1