A Simple TO-DO Python Flask Application With MongoDB

Introduction

 
We are going to create a simple Python web application using Flask framework and MongoDB. It is very easy to work with Flask as well as MongoDB.
Download Python from here.
 
You can select the customized installation choice for adding Python environment variables. If you choose default installation, please set these variables manually.
 
Python
 
Please click Add Python 3.7 to PATH option. It will add one entry in the environment variable.
 
Python 
 
If needed you can modify the installation path. Please check the Python environment variables.
 
After successful installation, if you check the system environment variable you can see the below two entries are added in the system.
 
Python 
 
We installed the Python 3.7 version, so it added as Python 37-32.
\Scripts folder is used for handling additional packages needed for Python.
 
Now you can check the Python and PIP version in the command prompt. PIP is the abbreviation for Python package index which is used for installing, upgrading or removing the additional Python libraries from our system.
 
>python --version and pip -- version
 
Python
 
By default, the pip version may not be 18. This is the latest version as of today. If your pip version is older than that, please upgrade it.
>python -m pip install -U pip
 
Now we are going to install Flask. Flask is a web framework for Python. Flask provides you with tools, libraries and technologies that allow you to build a web application in Python.
 
Its dependencies are,
  • Werkzeug a WSGI utility library
  • jinja2 which is its template engine
WSGI is basically a protocol defined so that the Python application can communicate with a web-server and thus be used as web-application outside of CGI.
 
Jinja2 is used for creating views in flask application. It holds all the static files like html, CSS and JavaScript files.
 
First install Flask using pip command in command prompt.
>pip install Flask
 
It will install the latest version of flask library from its global repository and in Windows machines it saves the below file location
C:\Program Files (x86)\Python\Python37-32\Lib\site-packages
 
Our sample application uses MongoDB as database. So, we are going to install Mongo DB from below URL.
https://www.mongodb.com/download-center#community
 
It is a free community version. After successful installation it is all set to run MongoDB instance.
 
Before running the MongoDB instance, we must create a data folder and run below command in command prompt.
"C:\Program Files\MongoDB\Server\4.0\bin\mongod.exe" --dbpath="C:\mongo-data"
 
Here C:\mongo-data folder is used for saving mongodb files.
 
While installing the MongoDB in windows system, you can choose the compass community edition also.
 
This is a free edition and will help to handle our MongoDB databases, collections (tables in MongoDB) and documents (records) graphically
 
Python 
 
By default, MongoDB is running in localhost 27017 port.
 
We can see there are 3 databases, (admin, config and local) created automatically in the installation time.
 
Python
 
Now we are going to install PyMongolibrary in python. PyMongo is a simple but powerful Python distribution containing tools for working with MongoDB and is the recommended way to work with MongoDB from Python. It's a kind of ODM (object document mapper). There is mongoengine and so many libraries also available as ODM for Python.
 
>pip install pymongo
 
We need to install another Python library, bson, too. It is used for getting objectId property of mongodb document.
 
>pip install bson
 
Now we are all set and ready to start our python program.
 
Create a folder named FlaskwithMongo and add two sub folders, static and templates, inside it. There is a convention to name static and templates used in jinja2 framework. Please open the folder in any of the code editors. I am using Visual Studio code as an IDE. Create a new file named app.py. This is the only Python file we are using in this application. 
 
First, we are going to import the required libraries into our application.
  1. from flask import Flask, render_template,request,redirect,url_for 
  2. from bson import ObjectId 
  3. from pymongo import MongoClient 
  4. import os  
    Now we are going to declare the app variable in our program.
    app = Flask(__name__)
     
    This app variable is used in the entire application.
     
    Now we are going to declare twovariable titles and headings which are later used in jinja2 template.
    1. title = "TODO sample application with Flask and MongoDB"  
    2. heading = "TODO Reminder with Flask and MongoDB" 
      We must declare the connection string for MongoDB and select a database. Also, we are going to select our collection name to a variable.
      1. client = MongoClient("mongodb://127.0.0.1:27017"#host uri  
      2. db = client.mymongodb #Select the database  
      3. todos = db.todo #Select the collection name 
        As I mentioned earlier mongodb is running in the port 27017 by default. Please note that we have selected mymongodb as database and todo as collection name. When we create our first transaction, pymongo will generate the database and collection automatically. Unlike SQL server or any other RDBMS mongodb doesn’t require predefined schemas.
         
        In flask, it is easy to implement routing unlike any other programming language.
        For that, we are using render_template, request, redirect and url_for. All these methods help us to establish redirection and show html templates in the browser
        1. def redirect_url():    
        2. return request.args.get('next'or \    
        3. request.referrer or \    
        4. url_for('index')   
          In the above code we defined a method named redirect_url and it is used to redirect page to index page.
          1. @app.route("/")    
          2. @app.route("/uncompleted")    
          3. def tasks ():    
          4. #Display the Uncompleted Tasks    
          5. todos_l = todos.find({"done":"no"})    
          6. a2="active"    
          7. return render_template('index.html',a2=a2,todos=todos_l,t=title,h=heading)   
          In the above code, we defined a method named tasks and it is used for two routes. One for default “/” route and another for “/uncompleted” route. In this code we defined a variable todos_l and it gets the documents from mongodb filter by condition done equal to no. We defined one more variable named a2 and it is used for controlling active records. Both todos_land a2 variables passed with the other two variables, title, and heading, and passed to the jinja2 template index.html which we must create in the template folder.
           
          Now we are going to finish all the remaining route definitions for adding and deleting the documents to mongodb
           
          app.py 
          1. from flask import Flask, render_template,request,redirect,url_for # For flask implementation    
          2. from bson import ObjectId # For ObjectId to work    
          3. from pymongo import MongoClient    
          4. import os    
          5.     
          6. app = Flask(__name__)    
          7. title = "TODO sample application with Flask and MongoDB"    
          8. heading = "TODO Reminder with Flask and MongoDB"    
          9.     
          10. client = MongoClient("mongodb://127.0.0.1:27017"#host uri    
          11. db = client.mymongodb    #Select the database    
          12. todos = db.todo #Select the collection name    
          13.     
          14. def redirect_url():    
          15.     return request.args.get('next'or \    
          16.            request.referrer or \    
          17.            url_for('index')    
          18.   
          19. @app.route("/list")    
          20. def lists ():    
          21.     #Display the all Tasks    
          22.     todos_l = todos.find()    
          23.     a1="active"    
          24.     return render_template('index.html',a1=a1,todos=todos_l,t=title,h=heading)    
          25.   
          26. @app.route("/")    
          27. @app.route("/uncompleted")    
          28. def tasks ():    
          29.     #Display the Uncompleted Tasks    
          30.     todos_l = todos.find({"done":"no"})    
          31.     a2="active"    
          32.     return render_template('index.html',a2=a2,todos=todos_l,t=title,h=heading)    
          33.   
          34.   
          35. @app.route("/completed")    
          36. def completed ():    
          37.     #Display the Completed Tasks    
          38.     todos_l = todos.find({"done":"yes"})    
          39.     a3="active"    
          40.     return render_template('index.html',a3=a3,todos=todos_l,t=title,h=heading)    
          41.   
          42. @app.route("/done")    
          43. def done ():    
          44.     #Done-or-not ICON    
          45.     id=request.values.get("_id")    
          46.     task=todos.find({"_id":ObjectId(id)})    
          47.     if(task[0]["done"]=="yes"):    
          48.         todos.update({"_id":ObjectId(id)}, {"$set": {"done":"no"}})    
          49.     else:    
          50.         todos.update({"_id":ObjectId(id)}, {"$set": {"done":"yes"}})    
          51.     redir=redirect_url()        
          52.     
          53.     return redirect(redir)    
          54.   
          55. @app.route("/action", methods=['POST'])    
          56. def action ():    
          57.     #Adding a Task    
          58.     name=request.values.get("name")    
          59.     desc=request.values.get("desc")    
          60.     date=request.values.get("date")    
          61.     pr=request.values.get("pr")    
          62.     todos.insert({ "name":name, "desc":desc, "date":date, "pr":pr, "done":"no"})    
          63.     return redirect("/list")    
          64.   
          65. @app.route("/remove")    
          66. def remove ():    
          67.     #Deleting a Task with various references    
          68.     key=request.values.get("_id")    
          69.     todos.remove({"_id":ObjectId(key)})    
          70.     return redirect("/")    
          71.   
          72. @app.route("/update")    
          73. def update ():    
          74.     id=request.values.get("_id")    
          75.     task=todos.find({"_id":ObjectId(id)})    
          76.     return render_template('update.html',tasks=task,h=heading,t=title)    
          77.   
          78. @app.route("/action3", methods=['POST'])    
          79. def action3 ():    
          80.     #Updating a Task with various references    
          81.     name=request.values.get("name")    
          82.     desc=request.values.get("desc")    
          83.     date=request.values.get("date")    
          84.     pr=request.values.get("pr")    
          85.     id=request.values.get("_id")    
          86.     todos.update({"_id":ObjectId(id)}, {'$set':{ "name":name, "desc":desc, "date":date, "pr":pr }})    
          87.     return redirect("/")    
          88.   
          89. @app.route("/search", methods=['GET'])    
          90. def search():    
          91.     #Searching a Task with various references    
          92.     
          93.     key=request.values.get("key")    
          94.     refer=request.values.get("refer")    
          95.     if(key=="_id"):    
          96.         todos_l = todos.find({refer:ObjectId(key)})    
          97.     else:    
          98.         todos_l = todos.find({refer:key})    
          99.     return render_template('searchlist.html',todos=todos_l,t=title,h=heading)    
          100.     
          101. if __name__ == "__main__":    
          102.     
          103.     app.run()   
          We must add the below three HTML files in the templates folder. (index.html, searchlist.html and update.html)
           
          index.html
          1. <html>    
          2.     <head>    
          3.         <title>{{t}}</title>    
          4.         <!-- href="/static/assets/style.css"-->    
          5.         <link rel="stylesheet" type="text/css"  href="{{ url_for('static',filename='assets/style.css')}}" >    
          6.         <link rel="stylesheet" type="text/css"  href="{{ url_for('static',filename='assets/emoji.css')}}" >    
          7.         <script src="{{ url_for('static',filename='assets/twemoji.min.js')}}"></script>      
          8.         <script src="{{ url_for('static',filename='assets/emoji.js')}}"></script>    
          9.     </head>    
          10. <body>    
          11.     <h1>{{ h }}</h1>    
          12.     <ul>    
          13.         <li><a href="/list" class="{{ a1 }}">ALL</a></li>    
          14.         <li><a href="/" class="{{ a2 }}">Uncompleted</a></li>    
          15.         <li><a href="/completed" class="{{ a3 }}">Completed</a></li>    
          16.     </ul>    
          17.     <hr>    
          18.     {% if todos[0] %}    
          19.     <div span="right">    
          20.     <form action="/search"   method="GET" >    
          21.         <table class="none" id="close">    
          22.         <tr>    
          23.         <td></td><td></td>    
          24.         <td><big><b>Search Reference:</b></big></td>    
          25.         <td><select name="refer" required>    
          26.             <option value="name">Task Name</option>    
          27.             <option value="desc">Description</option>    
          28.             <option value="date">Date</option>    
          29.             <option value="pr">Priority</option>    
          30.         </select></td>    
          31.         <td><input type="text" name="key" placeholder="Search Task" size="15" /></td>    
          32.         <td><button type="submit">Search</button></td>    
          33.         </tr>    
          34.         </table>    
          35.     </form>    
          36.     </div>    
          37.     <b><big>To-Do LIST :</big></b>    
          38.     <table>    
          39.         <tr id="row">    
          40.             <th class="status">Status</th>    
          41.             <th class="name">Task Name</th>    
          42.             <th class="desc">Description Name</th>    
          43.             <th class="date">Date</th>    
          44.             <th class="pr">Priority</th>    
          45.         <th class="func1">Remove</th>    
          46.         <th class="func2">Modify</th>    
          47.         </tr>    
          48.     {% for todo in todos %}    
          49.         <tr class="datas">    
          50.             <td><a href="./done?_id={{ todo['_id'] }}"><input type="image" src="static/images/{{todo['done']}}.png" alt="Submit ME"></a></td>    
          51.             <td class="name">{{ todo["name"] }}</td>    
          52.             <td class="desc">{{ todo["desc"] }}</td>    
          53.             <td class="date">{{ todo["date"] }}</td>    
          54.             <td class="pr">{{ todo["pr"] }}</td>    
          55.             <td class="func1"><a href="./remove?_id={{ todo['_id'] }}"><button type="submit">DELETE</button></a></td>    
          56.             <td class="func1"><a href="./update?_id={{ todo['_id'] }}"><button type="submit">EDIT</button></a></td>    
          57.         </tr>    
          58.     {% endfor %}    
          59.     </table>    
          60.     {% else %}    
          61.     <h4>No Tasks in the List !!</h4>    
          62.     {% endif %}    
          63.     <hr/>    
          64.     <form action="/action" method="POST">    
          65.     <table class="none">    
          66.         <tr>    
          67.             <td><b><big><label>Add a Task : </label></big></b></td>    
          68.         </tr>    
          69.         <tr>    
          70.         <td><input type="text" name="name" placeholder="Taskname" ></td>    
          71.         <td><textarea name="desc" rows="1" cols="30" placeholder="Enter Description here..." required></textarea></td>    
          72.         <td><input type="text" name="date" placeholder="Date" /></td>    
          73.         <td><input type="text" name="pr" placeholder="Priority" /></td>    
          74.         <td><button type="submit"> Create </button></td>    
          75.         </tr>    
          76.     </form>    
          77.     </table>    
          78.     <script>    
          79.         
          80.     </script>    
          81. </body>    
          82. </html>   
            searchlist.html
            1. <html>    
            2.     <head>    
            3.         <title>{{t}}</title>    
            4.         <link rel="stylesheet" type="text/css"  href="{{ url_for('static',filename='assets/style.css')}}" >    
            5.     </head>    
            6. <body>    
            7.     <h1>{{h}}</h1>    
            8.     <hr>    
            9.         {% if todos[0] %}    
            10.     <h3>Result of the Search : ToDO List</h3>    
            11.     <table>    
            12.         <tr id="row">    
            13.            <td class="status">Status</td>    
            14.             <td class="name">Task Name</td>    
            15.             <td class="desc">Task Description</td>    
            16.             <td class="date">Date</td>    
            17.             <td class="pr">Project</td>    
            18.         <td class="func">Delete</td>    
            19.         <td class="func">Modify</td>    
            20.         </tr>    
            21.         {% for todo in todos %}    
            22.         <tr class="datas">    
            23.             <td><a href="./done?_id={{ todo['_id'] }}"><input type="image" src="static/images/{{todo['done']}}.png" alt="Submit ME"></a></td>    
            24.             <td class="name">{{ todo["name"] }}</td>    
            25.             <td class="desc">{{ todo["desc"] }}</td>    
            26.             <td class="date">{{ todo["date"] }}</td>    
            27.             <td class="pr">{{ todo["pr"] }}</td>    
            28.             <td class="func1"><a href="./remove?_id={{ todo['_id'] }}"><button type="submit">DELETE</button></a></td>    
            29.             <td class="func1"><a href="./update?_id={{ todo['_id'] }}"><button type="submit">EDIT</button></a></td>    
            30.         </tr>    
            31.         {% endfor %}    
            32.     {% else %}    
            33.         <h4>No Result Found !!</h4>    
            34.     {% endif %}    
            35.     </table>    
            36.    <a href="/">Return to TaskList</a>    
            37. </body>    
            38. </html>  
              update.html
              1. <html>    
              2.    <head>    
              3.             <title>{{t}}</title>    
              4.         </head>    
              5.         <style>       
              6.             h1{    
              7.                 font-family:"Arial Black", Gadget, sans-serif;    
              8.             }    
              9.             body{    
              10.                 background-color:white;    
              11.             }    
              12.         </style>    
              13.     <body>    
              14.         <h1>{{h}}</h1>    
              15.         <hr>    
              16.         <h3>Update tasks with a reference</h3>    
              17.         <form action="/action3" method="POST">    
              18.         {% for task in tasks %}    
              19.             Unique Object ID : {{ task['_id'] }}<br/>    
              20.         <input type="text" name="_id" value="{{ task['_id'] }}" hidden>    
              21.         
              22.         <table>    
              23.         <tr>    
              24.         <td>Task Name</td><td> : </td><td><input type="text" name="name" value="{{ task['name'] }}" placeholder="{{ task['name'] }}"></td>    
              25.         </tr>    
              26.         <tr>    
              27.         <td>Description</td><td> : </td><td><textarea type="text" name="desc" rows=3 cols=30 placeholder="{{ task['desc'] }}"> {{ task['desc'] }} </textarea></td>    
              28.         </tr>    
              29.         <tr>    
              30.         <td>Date</td><td> : </td><td><input type="text" name="date" value="{{ task['date'] }}" placeholder="{{ task['date'] }}"></td>    
              31.         </tr>    
              32.         <tr>    
              33.         <td>Priority</td><td> : </td><td><input type="text" name="pr" value="{{ task['pr'] }}" placeholder="{{ task['pr'] }}"></td>    
              34.         </tr>    
              35.         </table>    
              36.         {% endfor %}    
              37.             <button type="submit"> Update Task </button>    
              38.             <br/>    
              39.         </form>    
              40.         <a href="/">Return to TaskList</a>    
              41. </body>    
              42. </html>   
                All three of these files used the features of jinja2 framework to render the model values from app.py.
                 
                In addition, we must add emoji.css, emoji.js, style.css and twemoji.min.js files in the static\assets folder
                 
                emoji.css 
                1. img.emoji {      
                2. // Override any img styles to ensure Emojis are displayed inline      
                3. margin0px !important;      
                4. displayinline !important;      
                5. }     
                emoji.js
                1. window.onload = function() {    
                2.       // Set the size of the rendered Emojis    
                3.       // This can be set to 16x16, 36x36, or 72x72    
                4.      twemoji.size = '16x16';    
                5.       // Parse the document body and    
                6.       // insert <img> tags in place of Unicode Emojis    
                7.       twemoji.parse(document.body);    
                8. }   
                  style.css
                  1. h1{    
                  2. /*  font-family:"Arial Black", Gadget, sans-serif;*/    
                  3.     font-family:"Times New Romans", Gadget, sans-serif;    
                  4. }    
                  5. body{    
                  6.    color:black;    
                  7.     background-color:white;    
                  8.     left-margin:10px;    
                  9.     right-margin:10px;    
                  10. }    
                  11. table{    
                  12.     width:95%;    
                  13.     border-collapse:collapse;    
                  14.     table-layout:fixed;    
                  15. }    
                  16. td    
                  17. {    
                  18.     border: rgba(2241197012px solid;    
                  19.     word-wrap:break-word;    
                  20. }    
                  21. table#close{    
                  22.        
                  23.     table-layout:default;    
                  24.     border-collapse:seperate;    
                  25.     border-spacing5px 5px;    
                  26.     border:none;    
                  27.     vertical-align:top;    
                  28. }    
                  29. table.none    
                  30. {    
                  31.     border:none;    
                  32.     vertical-align:top;    
                  33. }    
                  34. table.none td    
                  35. {    
                  36.     bordernone;    
                  37. }    
                  38. tr.row td{    
                  39.     text-decoration:italic;    
                  40. }    
                  41. th.status{    
                  42.     width:5%;    
                  43. }    
                  44. th.name{    
                  45.     width:20%;    
                  46. }    
                  47. th.des{    
                  48.     width:40%;    
                  49.     padding:5px 5px 5px 5px;    
                  50. }    
                  51. th.date{    
                  52.     width:8%;    
                  53. }    
                  54. th.pr{    
                  55.     width:7%;    
                  56. }    
                  57. th.func1{    
                  58.    width:6%;    
                  59. }    
                  60. th.func2{    
                  61.    width:5%;    
                  62. }    
                  63. input[type=submit] {    
                  64.     width20em;  height2em;    
                  65. }    
                  66. ul {    
                  67.     list-style-typenone;    
                  68.    margin0;    
                  69.    padding0;    
                  70.    overflowhidden;    
                  71.    background-color#333;    
                  72. }    
                  73. li {    
                  74.     floatleft;    
                  75. }    
                  76. li a {    
                  77.     displayblock;    
                  78.     colorwhite;    
                  79.    text-aligncenter;    
                  80.    padding14px 16px;    
                  81.    text-decorationnone;    
                  82. }    
                  83. a.active {    
                  84.    background-color#4CAF50;    
                  85. }    
                  86. /* Change the link color to #111 (black) on hover */    
                  87. li a:hover {    
                  88.     background-color#111;    
                  89. }   
                    There are two images, no.png, and yes.png, in static\images folder.
                     
                    We are ready to run our sample application.
                     
                    Please start our mongodb instance with the below command
                    "C:\Program Files\MongoDB\Server\4.0\bin\mongod.exe" --dbpath="C:\mongo-data"
                     
                    Please note that mongodb now runs with data folder C:\mongo-data
                     
                    Python 
                     
                    By default, it is listening the port 27017.
                     
                    Please open another command prompt window in the same Python application folder and run Python with the below command.
                    For example:
                    D:\Python\FlaskWithMongo>python app.py
                    Our local server is running in the port 5000 by default.
                     
                    Python 
                     
                    The application is now running successfully.
                     
                    Python 
                     
                    You can now add a sample task as task name “Test task for mongo with flask application”, description “Sample description”, date “05-08-2018" and priority “High”
                    After clicking the Create button we can immediately view the task details in the grid.
                     
                    Python 
                     
                    Please note that there is a new database named mymongodb and collection todo is created now. Using this application, you can edit and remove the existing documents easily.
                     
                    You can check the document details in the mongodb compass community too.
                     
                    Python
                     
                    Happy coding with Flask and MongoDB!!!
                     
                    You can download the source code from Github.