In part 2 of this tutorial we will cover first some basic MongoDB functions using both mongo prompt and using python library pymongo and finally we will update the register and login page to use the database .
As I mentioned in part 1 of this tutorial, we will be using MongoDB as our database.
MongoDB is an open-source document database and leading NoSQL database. A document oriented database means that the data is stored and queried as a JSON like format .
Two terms that are often mentioned with NoSQL are collections and documents .
I will not go in-depth about MongoDB but rather only the functions that we will use in this tutorial, If you want to learn more check this link. It's a good place to start .
Installing mongoDB:
I'm using an Ubuntu 18.04 so an apt install should do the trick . Check the install instructions for your Linux distribution if you are using a different one.
apt install mongodb
Once the install is complete, verify that mongoDB service is running :
systemctl status mongodbIf it's not active (running) start it using :
● mongodb.service - An object/document-oriented database
Loaded: loaded (/lib/systemd/system/mongodb.service; enabled; vendor preset: enabled)
Active: active (running) since Wed 2021-05-26 08:49:17 +01; 6h ago
Docs: man:mongod(1)
Main PID: 1236 (mongod)
Tasks: 23 (limit: 4915)
CGroup: /system.slice/mongodb.service
└─1236 /usr/bin/mongod --unixSocketPrefix=/run/mongodb --config /etc/mongodb.conf
systemctl start mongodb
If you want it to automatically starts on boot :
systemctl enable mongodb
Or disable automatic start on boot with :
systemctl disable mongodb
Preparing collections and documents
Let's cover now some basic MongoDB commands to prepare our Collections and documents .
Enter mongoDB prompt using :
mongo
MongoDB shell version v3.6.3
connecting to: mongodb://127.0.0.1:27017
MongoDB server version: 3.6.3
>
First we select a database or create a new one if it doesn't exists . I named it notes_app
> use notes_app
switched to db notes_app
You can check the database currently in use with :
> db
notes_app
To create a new Collection use the command :
> db.createCollection("notes")Go ahead and create the notes and users collections , we will be using those with our application .
{ "ok" : 1 }
> db.createCollection("users")
{ "ok" : 1 }
{
"username" : "ouslab",
"password" : "Some_password", "token" : "some_random_string"
}
So we have a username and password field , and also a token field that we will use once we add the Authorization later using JWT.
The JSON structure for the notes will be :
{Pretty basic as well, we have two fields , and a user_id field that we will use to specify which user the note belongs to .
"title" : "note title",
"description" : "note description",
"user_id" : ObjectId("609be9e9d54ed6102b351eb1")
}
Let's insert a user :
db.users.insert({ "username" : "ouslab", "password" : "Some_password ", "token" : "some_random_string" })
WriteResult({ "nInserted" : 1 })
To list the documents of a collection use the find function :
> db.users.find()
{ "_id" : ObjectId("60afbfb9c38dce109f9bccc7"), "username" : "ouslab", "password" : "Some_password ", "token" : "some_random_string" }
If we don't specify the _id parameter, then MongoDB assigns a unique ObjectId for this document.
We can filter the results of the find function by giving it the keys and desired value :
> db.users.find({"username":"ouslab"})
{
"_id" : ObjectId("60afbfb9c38dce109f9bccc7"),
"username" : "ouslab",
"password" : "Some_password ",
"token" : "some_random_string"
}
> db.users.find({"_id": ObjectId("60afbfb9c38dce109f9bccc7")})
{ "_id" : ObjectId("60afbfb9c38dce109f9bccc7"), "username" : "ouslab", "password" : "Some_password ", "token" : "some_random_string" }
The find function returns an array , if you want only one document, we will use the findOne() function :
> db.users.findOne({"username":"ouslab"})
{
"_id" : ObjectId("60afbfb9c38dce109f9bccc7"),
"username" : "ouslab",
"password" : "Some_password ",
"token" : "some_random_string"
}
To update a document we use the updateOne() function as follow :
> db.users.updateOne({"username":"ouslab"},{"$set": {"username":"some_username"}})
{ "acknowledged" : true, "matchedCount" : 1, "modifiedCount" : 1 }
> db.users.find()
{ "_id" : ObjectId("60afbfb9c38dce109f9bccc7"), "username" : "some_username", "password" : "Some_password ", "token" : "some_random_string" }
The first argument {"username":"ouslab"} selects the document we want to update, in this case the document that have username equals to "ouslab" and with the second argument we are requesting to update the username to "some_username"
And finally to delete a document we use the remove function :
> db.users.remove({"username" : "some_username"},1)
WriteResult({ "nRemoved" : 1 })
This will delete all documents that has "some_username" as username, and with the second argument set to 1 we are requesting to delete only the first one .
Installing pymongo
Use pip to install pymongo as well as bson which will provide functions to manipulate ObjectId of documents.
Make sure you are still in the active python virtual environment we have created in part 1 .
pip install pymongo bson
Let's thinker a bit with the library in a python interpreter prompt and review some of the CRUD functions we've tried above, but this time with python .
Open the python interpreter prompt and import pymongo and ObjectId from bson library :
python
Python 3.6.9 (default, Oct 8 2020, 12:12:24)
[GCC 8.4.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pymongo import MongoClient
>>> from bson.objectid import ObjectId
Then create an instance of MongoClient :
>>> client = MongoClient()
By default it is set to use the local mongoDB database, if you want to specify a specific ip and port , you can use :
>>> client = MongoClient('localhost',27017)
Then we select the database , in our case it will be notes_app :
>>> db=client.notes_app
We can insert a document with :
>>> db.users.insert({"username":"ouslab","password": "some_password","token":""})
ObjectId('60b0b66ec2a7d396353ac2f8')
We can list the documents with :
>>> users=db.users.find()
>>> list(users)
[{'_id': ObjectId('60b0b66ec2a7d396353ac2f8'), 'username': 'ouslab', 'password': 'some_password', 'token': ''}]
>>>
Notice that i have used the list function to convert from a mongoDB collection to python list
We can filter the the results same way we did in mongo prompt, this time we will use ObjectId to get a specific document :
>>> user=db.users.find_one({"_id":ObjectId('60b0b66ec2a7d396353ac2f8')})
>>> user
{'_id': ObjectId('60b0b66ec2a7d396353ac2f8'), 'username': 'ouslab', 'password': 'some_password', 'token': ''}
To update documents we use update or update_one function :
>>> user=db.users.find_one({"_id":ObjectId('60b0b66ec2a7d396353ac2f8')})
>>> db.users.update_one(user, {"$set" : { "username" : "new_username" } } )
<pymongo.results.UpdateResult object at 0x7f52e914aec8>
>>> user=db.users.find_one({"_id":ObjectId('60b0b66ec2a7d396353ac2f8')})
>>> user
{'_id': ObjectId('60b0b66ec2a7d396353ac2f8'), 'username': 'new_username', 'password': 'some_password', 'token': ''}
Notice here that i have fetched the document first in the user variable, then passed it to the update_one function as a first argument
And finally we can delete a document with delete or delete_one function :
>>> user=db.users.find_one({"_id":ObjectId('60b0b66ec2a7d396353ac2f8')})
>>> db.users.delete_one(user)
<pymongo.results.DeleteResult object at 0x7f52e917b108>
Update the Login and Register
Okay now that we have seen most of the necessary database operations we will need in our application, lets go back to Bottle .
In server.py we import pymongo and bson , and setup the mongoClient towards the beginning of the file :
from bottle import route, run, template ,static_file, redirect , request
import os
import json
import hashlib
from pymongo import MongoClient
from bson.objectid import ObjectId
client = MongoClient()
db=client.notes_app
...
Import hashlib as well, we will use it next in the user registration to make passwords hash before storing them in the database .
I will create two functions one that generates a hash of a password and the second verifies if the hashs of password and a hash string are equal. The functions will use the SHA-256 hashing algorithm using pbkdf2_hmac function of the hashlib library to learn more check out this blog
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
And now we can update our register_post function :
@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)
First we search in the database to see whether the username already exists using the find_one function, if not we create a hash of the password and we store the username, the hashed password and an empty token in the database . And finally we return a Json string containing the result of the operation .
Save and rerun the server.py python script , and try register a new user :
If you check in mongo prompt, you will find the user was created :
> db.users.find()
{ "_id" : ObjectId("60b0cb006cfc74e7c99128b8"), "username" : "ouslab", "password" : BinData(0,"bfy6QRF4gnwIlLi/6QJhzXqoQds7L7ymf6EeYfyIDcnzTzTi9r/05rNC95S5IlXGePRkICt0fd+zvw1HH+z+bA=="), "token" : "" }
Now for the authentication , let's create a new function with the following content :
@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']) :
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)
We simply search for the user by username , if it exists we compare the hash of the provided password with the hash stored in the database and finally we return a Json string with a specific error code and message depending on each case .
Don't forget to update the login.html page as well , it should look 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>Login</title>
<link rel="stylesheet" href="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/style.css">
</head>
<body>
<div class="login-form" id="app">
<form>
<h2 class="text-center">Log in</h2>
<div class="form-group">
<input type="text" class="form-control" placeholder="Username" required="required" name="username" id="username" v-model="username">
</div>
<div class="form-group">
<input type="password" class="form-control" placeholder="Password" required="required" name="password" id="password" v-model="password">
</div>
<div class="form-group">
<button type="button" class="btn btn-primary btn-block" v-on:click="login">Log in</button>
</div>
</form>
<p class="text-center"><a href="/register">Register</a></p>
</div>
<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>
var vue = new Vue({
el : "#app",
data: {
username: "",
password: ""
},
methods: {
login: function(){
var _this = this
const data = JSON.stringify({'username':_this.username,'password':_this.password});
axios.post('/authentication', data, {
headers: {'Content-Type': 'application/json'}})
.then(function (response) {
console.log(response.data);
if(response.data['error'] != 0){
console.log("No action");
}else{
window.location.href='/';
}
})
.catch(function (error) { console.log(error);});
},
}
});
</script>
</body>
</html>
rerun the server.py script , and try to login with the user that you registered previously, if it's successful you should be redirected to "/" and see the hello work page .
In the next part of the tutorial, we will implement the CRUD functionalities for the application.
Comments :