Create a light REST API web application using Bottle framework Vuejs and MongoDB Part3

VueJS_JWT_Python_Bottle framework_Rest API_API_MongoDB_PyMogo_Linux_

In part 2 we've prepared MongoDB database and updated the login and register pages, in this Part of the tutorial we will work on the dashboard page where we can manipulate the notes.

But before that, let's implement a logout functionality, however in order to do that we need some kind of session implementation, The simplest way is to use cookies:

  • When a user authenticates we store his ID in a cookie variable with some expiration time .
  • When a user logs out we delete the cookie variable.

So let's update our login function to create a cookie variable when the user successfully authenticates:

from bottle import route, run, template ,static_file, redirect, request, response
...
import os
import datetime
os.environ['TZ'] = 'Africa/Casablanca' # Set the timeZone

...
@route('/authentication', method='POST')
def authentication():
    username = request.json['username']
    password = request.json['password']
    user=db.users.find_one({"username":username})
    if user :        
        if verif_password_hash(password,user['password']) :
            timeLimit= datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
            response.set_cookie("username",user['username'],domain="localhost",expires=timeLimit)
            response.set_cookie("user_id",str(user['_id']),domain="localhost",expires=timeLimit)

            return_data = {"error": "0", "message": "Success"}
        else :
            return_data = {"error": "1", "message": "Fail"}
    else :
        return_data = {"error": "2", "message": "User doesn't exist"}   
    return json.dumps(return_data)


I used the datetime library to create an expiration time of 30 minutes that we will pass as arguments to the set_cookie function. I have created two cookies variables one stores the ID and the second the username.

For the Logout, create a new function that is linked to /logout URL using the @route decorator. In the logout we simply delete the cookies and redirects the user back to the login page.

@route('/logout')
def logout():
response.delete_cookie("username")
response.delete_cookie("user_id")
redirect("/login")

We will improve the login and logout once we add the authorization using the JWT tokens, for now this will do.

Let's update the home function to redirect the user to the login page if the cookies variable none existent :

@route('/')
def home():

    user_id = request.get_cookie("user_id")
    username = request.get_cookie("username")

    if user_id and username :
        return "Hello world <a href='/logout'> Logout </a>"
    else :
        redirect("/login")

Restart the server.py script, and go to the page "http://localhost:8080/", you should be redirected to the login page, and if you authenticate you will be redirected to the home page :


And if you click the logout link you will be redirected back to the login page.

Dashboard

Now we can proceed to the fun part.

Similarly to the login page, I'm going to grab an open source template snippet code that has a table that lists some elements and also buttons for the add, edit delete operation.

It's using JQuery for handling the UI and the events, but I will have to adjust it to work with VueJS instead.

Let's create a new file dashboard.html in views folder, an past a cleaned up version of the template snippet code, You should be left with something like this:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dashboard</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/googlefonts.css">
<link rel="stylesheet" href="/static/css/font-awesome/h5p-font-awesome.min.css">

</head>
<body>
    <div class="container">
    <nav class="navbar navbar-inverse">
            <div class="container-fluid">
                <div class="navbar-header">
                <a class="navbar-brand" href="/dashboard">
                    <p> <i class="fa fa-1x fa-user"  ></i> Username here</p>
                </a>
                </div>
                <a href="/logout" class="btn btn-default navbar-btn pull-right">Logout</a>
         </div>            
        </nav>

        <div class="table-wrapper">
            <div class="table-title">
                <div class="row">
                    <div class="col-sm-8"><h2> <b>Notes</b></h2></div>
                    <div class="col-sm-4">                     <button type="button" class="btn btn-info add-new"><i class="fa fa-plus"></i> Add New</button>
                    </div>
                </div>
            </div>
            <table class="table table-bordered">
                <thead>
                    <tr>
                        <th>ID</th>
                        <th>Note</th>
                        <th>Description</th>
                        <th>Actions</th>
                    </tr>
                </thead>
                <tbody>
                    <tr>
                        <td>11111111</td>
                        <td>Some title</td>
                        <td>Some Description</td>
                        <td>          <a class="add" title="Add" data-toggle="tooltip"><i class="material-icons">&#xE03B;</i></a>
<a class="edit" title="Edit" data-toggle="tooltip"><i class="material-icons">&#xE254;</i></a>
                         <a class="delete" title="Delete" data-toggle="tooltip"><i class="material-icons">&#xE872;</i></a>

                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>     
</body>
<script>
  $(document).ready(function(){
    
  });
</script>
</html>

Don't forget to download the CSS and Javascript files into the static folder if you want to serve them locally, or you can keep using the CDN URLs if you want to.

I have added a navigation bar so we can show the username currently logged in and also a logout button.


Let's update the home function to return the dashboard template instead of the hello world string :

@route('/')
def home():
    user_id = request.get_cookie("user_id")
    username = request.get_cookie("username")
    if user_id and username :
        return template('dashboard.html')
    else :
        redirect("/login")

Open http://localhost:8080/ in your browser,  and make sure that the dashboard page is loading correctly :


Add a notes

Let's create an API that adds a note to the database :

@route('/api/addnote' , method="POST")
def apiAddNote():
    user_id = request.get_cookie("user_id")
    user=db.users.find_one({"_id": ObjectId(user_id)})
    if user :
        data = request.json
        data['user_id']=ObjectId(user_id)
        db.notes.insert_one(data)
        return {"error": "0","message":"Success"}
    else :
        return {"error": "1","message":"Fail"}

We first get the user_id from the cookies, then we fetch the user document to make sure it exists, we add the current user_id to the JSON data and finally we insert the note in the database.

In the dashboard.html page, let's setup VueJS and Axios the same way we did with login and register page :

...
<body>
    <div class="container" id="app">
...
        <div class="table-wrapper">
            <div class="table-title">
                <div class="row">
                    <div class="col-sm-8"><h2> <b>Notes</b></h2></div>
                    <div class="col-sm-4"> <button type="button" class="btn btn-info add-new" v-on:click="new_note()" ><i class="fa fa-plus" ></i> Add New</button>
                    </div>
                </div>
            </div>
            <table class="table table-bordered">
             ...
                <tbody>
                    <tr>
                        <td>11111111</td>
                        <td>Some title</td>
                        <td>Some Description</td>
                        <td>                            <a class="edit" title="Edit" data-toggle="tooltip"  ><iclass="material-icons">&#xE254;</i></a>
<a class="delete" title="Delete" data-toggle="tooltip" ><i class="material-icons">&#xE872;</i></a>
</td>
                    </tr>
                    <tr v-if="new_row">
                        <td></td>     <td><input type="text" class="form-control" v-bind:class="(!note_buffer.title)?'error':''" name="title" id="title" v-model="note_buffer.title"></td> <td><input type="text" class="form-control" v-bind:class="(!note_buffer.description)?'error':''" name="description" id="description" v-model="note_buffer.description"></td>
                        <td>
                <a class="add" title="Add" data-toggle="tooltip" v-on:click="add_note()" ><i class="material-icons">&#xE03B;</i></a>
                <a class="delete" title="Delete" data-toggle="tooltip" v-on:click="cancel_add()" ><i class="material-icons">&#xE872;</i></a>
                        </td>
                    </tr>
                </tbody>
            </table>
        </div>
    </div>     
</body>
<script src="/static/js/jquery-2.2.4.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/vue2.js"></script>
<script src="/static/js/axios.min.js"></script>

<script>
  $(document).ready(function(){
    var vue = new Vue({
            el : "#app",
            data: {
                note_buffer:{title:"",description:""},
                new_row:0
            },
            methods: {
                new_note: function(){
                    this.new_row = 1;
                },
                add_note: function(){

                   var _this = this;
           if(!_this.note_buffer.title || !_this.note_buffer.description){  
                        return
                    }
                    const data = JSON.stringify(_this.note_buffer);
                    
                    axios.post('/api/addnote', data, {
                        headers: {'Content-Type': 'application/json'}})
                    .then(function (response) {
                        _this.note_buffer={title:"",description:""}
                        _this.new_row = 0;  
                    }).catch(function (error) { console.log(error);});
                },
                cancel_add: function(){
                    this.new_row=0;
                }
            }    
        });

  });
</script>
</html>

So what I've done here is first added the element tag id="app" to be detected by VueJ.

Then added a note_buffer variable that will be bound to the title and description inputs with v-model="note_buffer.title" and v-model="note_buffer.description", I'm using the buffer variable also to add the CSS class error to the inputs with v-bind:class="(!note_buffer.title)?'error':''" so that way if the inputs are empty they will be highlighted with a red border .

I have Added also a flag variable new_row that we set to 1 when we click on the Add New button by calling the new_note() function, which is specified with v-on:click="new_note()", and the same way the variable is set to 0 when trash icon in the new row is clicked via the cancel_add function specified with v-on:click="cancel_add().

The flag variable controls whether to show or hide the the new row with the v-if="new_row" tag.

And finally i call the add_note function upon the click event on the green add icon. In that function I first make sure that both the title and description are not empty, then I convert the buffer to a JSON format that i pass later on as an argument to the Axios post function along with the API URL that we created "/api/addnote".

Try adding a new note :

And then verify in mongoDB to make sure the notes was inserted correctly :

> db.notes.find()
{ "_id" : ObjectId("60b4f0384a4f53b68d81c123"),"title" : "new_note2", "" : "new_note2 desc", "user_id" : ObjectId("60b0cb006cfc74e7c99128b8") }

Currently we are not listing the notes from the database, let's fix that.

Listing the notes:

Create a new  API :

@route('/api/listnotes',method="GET")
def apiListNotes():
    user_id = request.get_cookie("user_id")
    notes=db.notes.find({'user_id':ObjectId(user_id)})
    #list_notes = list(notes)    
    list_notes = [ {'_id':str(ll['_id']),'title':ll['title'],'description':ll['description']} for ll in notes ]
    return json.dumps({"notes":list_notes})

The find function returns results like this :

[{'_id': ObjectId('60b4f0384a4f53b68d81c123'), 'title': 'new_note', 'description': 'new_note'}, 
 {'_id': ObjectId('60b4f14e4a4f53b68d81c124'), 'title': 'new_note2', 'description': 'new_note2 desc'}]

It would be better to get rid of the ObjectID and have a string instead like this :

[{'_id': '60b4f0384a4f53b68d81c123', 'title': 'new_note', 'description': 'new_note'}, 
{'_id': '60b4f14e4a4f53b68d81c124', 'title': 'new_note2', 'description': 'new_note2 desc'}]

I achieved that using this inline for loop that iterate over each element and convert the ObjectID to string :

list_notes = [ {'_id':str(ll['_id']),'title':ll['title'],'description':ll['description']} for ll in notes ]

And at the end we convert and return the list as JSON.

In the dashboard.html we'll adjust few things :

...
<tbody>
     <tr v-for="(note,index) in notes">
        <td>{{ "{{ note['_id'] }}" }}</td>
        <td>{{ "{{ note['title'] }}" }}</td>
        <td>{{ "{{ note['description'] }}" }}</td>
         <td> <a class="edit" title="Edit" data-toggle="tooltip"  ><i class="material-icons">&#xE254;</i></a>
 <a class="delete" title="Delete" data-toggle="tooltip"  ><i class="material-icons">&#xE872;</i></a>
         </td>                       
     </tr>

...
    var vue = new Vue({
            el : "#app",
            data: {
                notes : [],
                note_buffer:{title:"",description:""},
                new_row:0
            },
            mounted: function() {
                this.get_list()
            },

            methods: {
                new_note: function(){
                    this.new_row = 1;
                },
                get_list: function(){
                    var _this = this;
                    axios.get('/api/listnotes')
                    .then(function (response) {
                        _this.notes = response.data['notes'];
                    })
                    .catch(function (error) { console.log(error);});
                },

                  add_note: function(){
                     ...
                    axios.post('/api/addnote', data, {
                        headers: {'Content-Type': 'application/json'}})
                    .then(function (response) {
                        _this.note_buffer={title:"",description:""}
                        _this.new_row = 0;
                        _this.get_list();  
                    }).catch(function (error) { console.log(error);});
                },
...

I have added a function get_list that pulls the notes list from the API we just created using the Axios GET function axios.get('/api/listnotes').

Then we store the result in a the variable notes I've added to the VueJS Data section with  _this.notes = response.data['notes']; .

In order to make sure we are getting fresh list if we reload the page, We have to  add the get_list function in the mounted section of VueJS , all functions that are listed there will be called on the page load.

Now that we have the list of notes stored in the variable notes, only left to loop through and display them in the table, to do so we use the VueJS v-for feature :

<tr v-for="(note,index) in notes">
<td>{{ "{{ note['_id'] }}" }}</td>
<td>{{ "{{ note['title'] }}" }}</td>
<td>{{ "{{ note['description'] }}" }}</td>
...
</tr>

The v-for loop will create a <tr></tr> tag for each element and returns a note and an index, then we can simply display the note details using {{ note['title'] }}, But Because both Bottle and VueJS uses the "{{}}" to display the variables we have to encapsulate one inside an other using double quotes.

The dashboard.html should look something like this so far :

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dashboard</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/style.css">
<link rel="stylesheet" href="/static/css/googlefonts.css">
<link rel="stylesheet" href="/static/css/font-awesome/h5p-font-awesome.min.css">
</head>
<body>
<div class="container" id="app">
<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="/dashboard">
<p> <i class="fa fa-1x fa-user" ></i> Username here</p>
</a>
</div>
<a href="/logout" class="btn btn-default navbar-btn pull-right">Logout</a>
</div>
</nav>
<div class="table-wrapper">
<div class="table-title">
<div class="row">
<div class="col-sm-8"><h2> <b>Notes</b></h2></div>
<div class="col-sm-4">
<button type="button" class="btn btn-info add-new" v-on:click="new_note()" ><i class="fa fa-plus" ></i> Add New</button>
</div>
</div>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="(note,index) in notes">
<td>{{ "{{ note['_id'] }}" }}</td>
<td>{{ "{{ note['title'] }}" }}</td>
<td>{{ "{{ note['description'] }}" }}</td>
<td>
<a class="edit" title="Edit" data-toggle="tooltip" ><i class="material-icons">&#xE254;</i></a>
<a class="delete" title="Delete" data-toggle="tooltip" ><i class="material-icons">&#xE872;</i></a>
</td>
</tr>
<tr v-if="new_row">
<td></td>
<td><input type="text" class="form-control" v-bind:class="(!note_buffer.title)?'error':''" name="title" id="title" v-model="note_buffer.title"></td>
<td><input type="text" class="form-control" v-bind:class="(!note_buffer.description)?'error':''" name="description" id="description" v-model="note_buffer.description"></td>
<td>
<a class="add" title="Add" data-toggle="tooltip" v-on:click="add_note()" ><i class="material-icons">&#xE03B;</i></a>
<a class="delete" title="Delete" data-toggle="tooltip" v-on:click="cancel_add()" ><i class="material-icons">&#xE872;</i></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</body>
<script src="/static/js/jquery-2.2.4.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/vue2.js"></script>
<script src="/static/js/axios.min.js"></script>
<script>
$(document).ready(function(){
var vue = new Vue({
el : "#app",
data: {
notes : [],
note_buffer:{title:"",description:""},
new_row:0
},
mounted: function() {
this.get_list()
},
methods: {
new_note: function(){
this.new_row = 1;
},

get_list: function(){
var _this = this;
axios.get('/api/listnotes')
.then(function (response) {
_this.notes = response.data['notes'];
})
.catch(function (error) { console.log(error);});
},
add_note: function(){
var _this = this;
if(!_this.note_buffer.title || !_this.note_buffer.description){
return
}
const data = JSON.stringify(_this.note_buffer);

axios.post('/api/addnote', data, {
headers: {'Content-Type': 'application/json'}})
.then(function (response) {
_this.note_buffer={title:"",description:""}
_this.new_row = 0;
_this.get_list();
}).catch(function (error) { console.log(error);});
}
cancel_add: function(){
this.new_row=0;
}
}
});
});
</script>
</html>

And if you check the dashboard page now you should see the list of notes :


Delete a notes:

Similarly to the add notes, we'll create a apiDeleteNote POST API , and then we call it using the Axios post function while providing the ID of the note :

@route('/api/deletenote' , method="POST")
def apiDeleteNote():
    id=request.json['id']
    note=db.notes.find_one({"_id": ObjectId(id)})
    if  note :
        db.notes.delete_one(note)
        return {"error": "0","message":"Success"}
    else :
        return {"error": "1","message":"Fail"}

We get the note id from the json data of the post request , then fetch the note from the database using the find_one function  by ObjectId, if the document exists we delete it using the delete_one function.

In dashboard.html, we add a delete_note function, and we update the the delete button tag to call the function on the click event:

...
<a class="delete" title="Delete" data-toggle="tooltip" v-on:click="delete_note(note['_id'])" ><i class="material-icons">&#xE872;</i></a>

...
delete_note: function(id){
var _this = this
const data = JSON.stringify({"id": id});
axios.post('/api/deletenote', data, {
headers: {'Content-Type': 'application/json'}})
.then(function (response) {
console.log(response.data);
_this.get_list();
})
.catch(function (error) { console.log(error);});
},

...

Notice that we are passing the note id as an argument to the delete_note function, and then we simply convert it to JSON and passing it to the Axios POST request function along with the URL "/api/deletenote".

Update a note:

I kept the edition of the note till the end as it's a bit tricky, because we have to make the cells of a row editable by changing them to html input tags, fortunately VueJS will facilitate that compared to the original JQuery code the template came with .

You should be familiarized with the steps by now, we start by creating an API for the update in server.py:

@route('/api/updatenote' , method="POST")
def apiUpdateNote():
data=request.json
note=db.notes.find_one({"_id": ObjectId(data['_id'])})
if note :
db.notes.update_one(note,{"$set" : {"title":data['title'],"description":data['description']}})
return {"error": "0","message":"Success"}
else :
return {"error": "1","message":"Fail"}

We get the JSON data from the post request, we fetch the note by ID , and finally we update the note using the update_one function .

In dashboard.html  we'll update v-for  section as follow :

<tr v-for="(note,index) in notes">
<template v-if="edit == index">
<td>{{ "{{ note['_id'] }}" }}</td>
<td><input type="text" class="form-control" v-bind:class="(!note.title)?'error':''" name="title" id="title" v-model="note_buffer.title"></td>
<td><input type="text" class="form-control" v-bind:class="(!note.description)?'error':''" name="description" id="description" v-model="note_buffer.description"></td>
<td>
<a class="add" title="Add" data-toggle="tooltip" v-on:click="update_note()" ><i class="material-icons">&#xE03B;</i></a>
<a class="delete" title="Delete" data-toggle="tooltip" v-on:click="cancel_add()" ><i class="material-icons">&#xE872;</i></a>
</td>
</template>
<template v-else >

<td>{{ "{{ note['_id'] }}" }}</td>
<td>{{ "{{ note['title'] }}" }}</td>
<td>{{ "{{ note['description'] }}" }}</td>
<td>
<a class="edit" title="Edit" data-toggle="tooltip" v-on:click="edit_note(index)" ><i class="material-icons">&#xE254;</i></a>
<a class="delete" title="Delete" data-toggle="tooltip" v-on:click="delete_note(note['_id'])" ><i class="material-icons">&#xE872;</i></a>
</td>
</template>
</tr>

For each row I'm using the v-if to  test the row's index equality to a variable edit that we will add to the VueJS data section, depending on the outcome we will displaying either one of the portions of code within the <template> tags.

The first template portion of code have html input tags, so we can edit the note details, and an update icon button instead of the edit icon .

To set which row to edit, we simply update the edit variable to the index of the row,  I'm doing that through the v-on:click="edit_note(index)" tag in the edit button :

...
$(document).ready(function(){
var vue = new Vue({
el : "#app",
data: {
notes : [],
note_buffer:{title:"",description:""},
new_row:0,
edit:-1,
},
...
edit_note: function(index){
var _this = this;
_this.edit=index;
_this.note_buffer=_this.notes[index];
},

...

Notice that i'm updating the note_buffer with the details of the row that is currently edited, and also i set the edit variable to -1 by default so it doesn't much any row index .

And finally on the click event of the update button v-on:click="update_note()", we call the update_note function :

update_note: function(){
var _this = this
const data = JSON.stringify(_this.note_buffer);
axios.post('/api/updatenote', data, {
headers: {'Content-Type': 'application/json'}})
.then(function (response) {
_this.get_list();
_this.note_buffer={_id:0,title:"",description:""}
_this.edit=-1;
}).catch(function (error) { console.log(error);});
},

Similarly to the add_note  we send the data through the update API we created  except we are including the id of the note in the JSON data this time :


The content of server.py and dashboard.html files should look something like this :

from bottle import route, run, template ,static_file, redirect , request, response
import os
import json
from pymongo import MongoClient
from bson.objectid import ObjectId
import hashlib

import os
import datetime
os.environ['TZ'] = 'Africa/Casablanca'

client = MongoClient()
db=client.notes_app

def password_hash(password):

salt= os.urandom(32)
key = hashlib.pbkdf2_hmac('sha256', password.encode('utf-8'),salt,100000)
return salt+key

def verif_password_hash(password,key):
hash_key = hashlib.pbkdf2_hmac('sha256',password.encode('utf-8'),key[:32],100000)
return key[32:] == hash_key

@route('/')
def home():
user_id = request.get_cookie("user_id")
username = request.get_cookie("username")
if user_id and username :
return template('dashboard.html')
else :
redirect("/login")

@route('/static/<filepath:path>')
def callback(filepath):
static_path = os.path.dirname(os.path.realpath(__file__))
return static_file(filepath, root=os.path.join(static_path,"static"))

@route('/login')
def login():
return template('login.html')

@route('/authentication', method='POST')
def authentication():
username = request.json['username']
password = request.json['password']

user=db.users.find_one({"username":username})
if user :
if verif_password_hash(password,user['password']) :
timeLimit= datetime.datetime.utcnow() + datetime.timedelta(minutes=30)
response.set_cookie("username",user['username'],domain="localhost",expires=timeLimit)
response.set_cookie("user_id",str(user['_id']),domain="localhost",expires=timeLimit)
return_data = {"error": "0", "message": "Success"}
else :
return_data = {"error": "1", "message": "Fail"}
else :
return_data = {"error": "2", "message": "User doesn't exist"}

return json.dumps(return_data)

@route('/logout')
def logout():
response.delete_cookie("username")
response.delete_cookie("user_id")
redirect("/login")

@route('/register')
def login():
return template('register.html')

@route('/register', method='POST')
def register_post():
username = request.json['username']
password = request.json['password']

user=db.users.find_one({"username":username})
if user :
return_data = { "error": "1", "message": "User exists"}
else :

password = password_hash(password)
db.users.insert_one({"username":username,"password":password,"token":""})
return_data = { "error": "0", "message": "Success"}

return json.dumps(return_data)

@route('/api/addnote' , method="POST")
def apiAddNote():
user_id = request.get_cookie("user_id")
user=db.users.find_one({"_id": ObjectId(user_id)})
if user :
data = request.json
data['user_id']=ObjectId(user_id)
db.notes.insert_one(data)
return {"error": "0","message":"Success"}
else :
return {"error": "1","message":"Fail"}

@route('/api/listnotes',method="GET")
def apiListNotes():
user_id = request.get_cookie("user_id")
notes=db.notes.find({'user_id':ObjectId(user_id)})
#list_notes = list(notes)
list_notes = [ {'_id':str(ll['_id']),'title':ll['title'],'description':ll['description']} for ll in notes ]
return json.dumps({"notes":list_notes})

@route('/api/updatenote' , method="POST")
def apiUpdateNote():
data=request.json
note=db.notes.find_one({"_id": ObjectId(data['_id'])})
if note :
db.notes.update_one(note,{"$set" : {"title":data['title'],"description":data['description']}})
return {"error": "0","message":"Success"}
else :
return {"error": "1","message":"Fail"}

@route('/api/deletenote' , method="POST")
def apiDeleteNote():
id=request.json['id']
print(id)
note=db.notes.find_one({"_id": ObjectId(id)})
if note :
db.notes.delete_one(note)
return {"error": "0","message":"Success"}
else :
return {"error": "1","message":"Fail"}

if __name__ == "__main__":
run(host='localhost', port=8080, debug=True)


<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Dashboard</title>

<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/style.css">

<link rel="stylesheet" href="/static/css/googlefonts.css">
<link rel="stylesheet" href="/static/css/font-awesome/h5p-font-awesome.min.css">


</head>
<body>
<div class="container" id="app">

<nav class="navbar navbar-inverse">
<div class="container-fluid">
<div class="navbar-header">
<a class="navbar-brand" href="/dashboard">
<p> <i class="fa fa-1x fa-user" ></i> Username here</p>
</a>
</div>
<a href="/logout" class="btn btn-default navbar-btn pull-right">Logout</a>
</div>

</nav>

<div class="table-wrapper">
<div class="table-title">
<div class="row">
<div class="col-sm-8"><h2> <b>Notes</b></h2></div>
<div class="col-sm-4">
<button type="button" class="btn btn-info add-new" v-on:click="new_note()" ><i class="fa fa-plus" ></i> Add New</button>
</div>
</div>
</div>
<table class="table table-bordered">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Description</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr v-for="(note,index) in notes">

<template v-if="edit == index">
<td>{{ "{{ note['_id'] }}" }}</td>
<td><input type="text" class="form-control" v-bind:class="(!note.title)?'error':''" name="title" id="title" v-model="note_buffer.title"></td>
<td><input type="text" class="form-control" v-bind:class="(!note.description)?'error':''" name="description" id="description" v-model="note_buffer.description"></td>
<td>
<a class="add" title="Add" data-toggle="tooltip" v-on:click="update_note()" ><i class="material-icons">&#xE03B;</i></a>
<a class="delete" title="Delete" data-toggle="tooltip" v-on:click="cancel_add()" ><i class="material-icons">&#xE872;</i></a>
</td>
</template>

<template v-else >
<td>{{ "{{ note['_id'] }}" }}</td>
<td>{{ "{{ note['title'] }}" }}</td>
<td>{{ "{{ note['description'] }}" }}</td>
<td>
<a class="edit" title="Edit" data-toggle="tooltip" v-on:click="edit_note(index)" ><i class="material-icons">&#xE254;</i></a>
<a class="delete" title="Delete" data-toggle="tooltip" v-on:click="delete_note(note['_id'])" ><i class="material-icons">&#xE872;</i></a>
</td>
</template>
</tr>

<tr v-if="new_row">
<td></td>
<td><input type="text" class="form-control" v-bind:class="(!note_buffer.title)?'error':''" name="title" id="title" v-model="note_buffer.title"></td>
<td><input type="text" class="form-control" v-bind:class="(!note_buffer.description)?'error':''" name="description" id="description" v-model="note_buffer.description"></td>
<td>
<a class="add" title="Add" data-toggle="tooltip" v-on:click="add_note()" ><i class="material-icons">&#xE03B;</i></a>
<a class="delete" title="Delete" data-toggle="tooltip" v-on:click="cancel_add()" ><i class="material-icons">&#xE872;</i></a>
</td>
</tr>

</tbody>
</table>
</div>
</div>
</body>

<script src="/static/js/jquery-2.2.4.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
<script src="/static/js/vue2.js"></script>
<script src="/static/js/axios.min.js"></script>

<script>

$(document).ready(function(){

var vue = new Vue({
el : "#app",
data: {
notes : [],
note_buffer:{title:"",description:""},
new_row:0,
edit:-1,
},
mounted: function() {
this.get_list()
},
methods: {
new_note: function(){
this.new_row = 1;
},

get_list: function(){
var _this = this;
axios.get('/api/listnotes')
.then(function (response) {
_this.notes = response.data['notes'];
})
.catch(function (error) { console.log(error);});
},
add_note: function(){
var _this = this;
if(!_this.note_buffer.title || !_this.note_buffer.description){
return
}
const data = JSON.stringify(_this.note_buffer);

axios.post('/api/addnote', data, {
headers: {'Content-Type': 'application/json'}})
.then(function (response) {
_this.note_buffer={title:"",description:""}
_this.new_row = 0;
_this.get_list();
}).catch(function (error) { console.log(error);});

},
edit_note: function(index){
var _this = this;
_this.edit=index;
_this.note_buffer=_this.notes[index];

},

update_note: function(){
var _this = this
const data = JSON.stringify(_this.note_buffer);
axios.post('/api/updatenote', data, {
headers: {'Content-Type': 'application/json'}})
.then(function (response) {
_this.get_list();
_this.note_buffer={_id:0,title:"",description:""}
_this.edit=-1;
}).catch(function (error) { console.log(error);});
},

delete_note: function(id){
var _this = this
const data = JSON.stringify({"id": id});
axios.post('/api/deletenote', data, {
headers: {'Content-Type': 'application/json'}})
.then(function (response) {
console.log(response.data);
_this.get_list();
})
.catch(function (error) { console.log(error);});
},
cancel_add: function(){
this.new_row=0;
}
}
});
});
</script>

</html>

You can check the final project from the github repository .

In the next part of the tutorial we will implement the JWT to restrict the acces to our APIs .

Comments :