Hello everyone, I thought that by creating this tutorial about how to build a search page in Django along with Amazon Dynamodb and React might help you guys to understand more about those trending technologies. First off, I would like to briefly talk about the search page. As you guys can imagine, a search page is a place where you allow user to search for what they want. For example, you would like to find a billionaire whose net worth is under 50 Billion and currently lives in Palo Alto, CA. For simplicity, we will be ignoring on how you would set up a Django environment in your own computer. If you have not done so, please go to here. Also, we will be skipping on how to set up Amazon dynamodb as well. If you have trouble with setting up for Amazon Dynamodb, please go to here for more detailed instruction. Now, before we do anything, we should fill in some dummy datas into the database for us to use later. Here is what the data looks like in the database.
Screenshots of dummy data in the database
Now, we need to add the url search into the url pattern in the Django in order for it to work like this, http://127.0.0.1:8000/search. To do that in Django, we can configure it in urls.py.
Add search url into the urls.py
from django.conf.urls import url
urlpatterns = [
# http://127.0.0.1:8000/search
# http://127.0.0.1:8000/search/
url(r'^search/?', views.search, name='search'),
]
Then, we need to create a HTML page for search so that the search method would know which page it should render. In Django, you may simply add the HTML page in the folder named templates. If you are not familiar with templates in Django, be sure to check out this site.
Sample of what the search.html looks like
Hello there!
Now, we may go to the views.py, this file should be side by side with the urls.py, the one you modified earlier. Add these piece of codes to notify the search method to render the search.html page.
Told the views.py to render the search.html page
from django.shortcuts import render
from django.template import loader
from django.http import HttpResponse
# http://127.0.0.1:8000/search
# http://127.0.0.1:8000/search/
def search(request):
template = loader.get_template('search.html')
context = {}
return HttpResponse(template.render(context, request))
Try go to the http://127.0.0.1:8000/search and see if you can see the Hello there! If you can see it, that means that you did it correctly. If you cannot, try to turn on your debug mode and see what went wrong! Next, in order for the search page to work, we need to retrieve the value of the GET parameter from the requested url. For example, say you need to find the billionaire whose net worth is under 50 Billion, and the requested url might look something like this, http://127.0.0.1:8000/search?net_worth_under=50. Or you are interested in billionaire who is currently located in New York, then the query might look something like this, http://127.0.0.1:8000/search?current_residence=New York, NY. Here is what the method might look like in order to get the values of GET parameters.
Method to retrieve the value of the GET parameter from the requested url
def retrieve_all_get_parameters(request):
param = {}
net_worth_under = request.GET.get('net_worth_under')
current_residence = request.GET.get('current_residence')
if net_worth_under != None and net_worth_under != '':
param['net_worth_under'] = net_worth_under
if current_residence != None and current_residence != '':
param['current_residence'] = current_residence
return param
Now, let’s modify the search method a little bit to see if the method retrieve_all_get_parameters is working properly.
Check to see if the method retrieve_all_get_parameters is working properly
from django.shortcuts import render
from django.template import loader
from django.http import HttpResponse
# http://127.0.0.1:8000/search
# http://127.0.0.1:8000/search/
def search(request):
template = loader.get_template('search.html')
params = retrieve_all_get_parameters(request)
return HttpResponse(template.render(params, request))
search.html
{{ net_worth_under }}<br>
{{ current_residence }}
Try type in this, http://127.0.0.1:8000/search?net_worth_under=50¤t_residence=New York, NY and you should be able to see your result print out on the page! Once we have this, we may move to the next level. Try to implment the code in such a way that when user comes to the search page, it first returns all the billionaire from the database.
Made database connection and simply return all the billionaire from the database
def get_list_of_billionaires(param):
try:
table = dynamodb.Table('put_your_amazon_dynamodb_table_name_here')
except botocore.exceptions.ClientError as e:
# http://stackoverflow.com/questions/33068055/boto3-python-and-how-to-handle-errors
return 'failed'
else:
response = table.scan()
if response['ResponseMetadata']['HTTPStatusCode'] == 200:
try:
item = response['Item']
except KeyError:
return None
return item
In method search,
def search(request):
template = loader.get_template('search.html')
params = retrieve_all_get_parameters(request)
billionaires = get_list_of_billionaires(params)
context = {
'billionaires' : billionaires
}
return HttpResponse(template.render(context, request))
In search.html,
{% for billionaire in billionaires %}
{{ billionaire.first_name }} {{ billionaire.last_name }}
{{ billionaire.source_of_wealth }}
{{ billionaire.current_location }}
{{ billionaire.net_worth }}
{% endfor %}
Now, when you go to http://127.0.0.1:8000/search, you should be able to see something like this.
Screenshots of printing out all the billionaire in the database
It seems to be working fine. Now, let’s work on the filtering part in the search page. There are few ways of doing filtering with Amazon Dynamodb, we will be using scan operation as our database is consider small. In production, you should use query instead for better performace. For more information about this, go ahead and read this offical document by Amazon. First, we need to work on the filter expression that is designed for Amazon Dynamodb. Your code should look something like this.
In method get_list_of_billionaires,
def get_list_of_billionaires(param):
filter_expression = []
expression_attribute_names = {}
expression_attribute_values = {}
try:
net_worth_under = param['net_worth_under']
filter_expression.append('(#net_worth <= :net_worth_under)')
expression_attribute_names['#net_worth'] = 'net_worth'
expression_attribute_values[':net_worth_under'] = net_worth_under
except KeyError:
pass
try:
current_residence = param['current_residence']
filter_expression.append('(#l = :current_residence)')
expression_attribute_names['#l'] = 'current_location'
expression_attribute_values[':current_residence'] = current_residence
except KeyError:
pass
So, at the end, in the array of filter_expression, we will have all the necessary query if needed. Attribute names and values are for setting the values for variables in the filter expression, so that later on, we can do the comparison operation with the query. Next, we need to write a method to combine all the query in the filter_expression with the ‘and’. For example, the query might look something like this, (#net_worth <= :net_worth_under) and (#l = :current_residence)
Method to combine queries into one
def filter_expression_to_string(filter_expression):
if not filter_expression:
return ''
length_of_filter_expression = len(filter_expression)
filter_expression_to_string = ''
for x in range(length_of_filter_expression):
filter_expression_to_string += filter_expression[x]
if x + 1 != length_of_filter_expression:
filter_expression_to_string += ' and '
return filter_expression_to_string
Once we have this, we can easily do the filtering part by doing this.
In method get_list_of_billionaires,
try:
table = dynamodb.Table('put_your_amazon_dynamodb_table_name_here')
except botocore.exceptions.ClientError as e:
# http://stackoverflow.com/questions/33068055/boto3-python-and-how-to-handle-errors
return 'failed'
else:
filtered_string = filter_expression_to_string(filter_expression)
if filtered_string != '' and expression_attribute_names and expression_attribute_values:
response = table.scan(
FilterExpression = filtered_string,
ExpressionAttributeNames = expression_attribute_names,
ExpressionAttributeValues = expression_attribute_values,
)
else:
response = table.scan(
ReturnConsumedCapacity = 'TOTAL',
)
if response['ResponseMetadata']['HTTPStatusCode'] == 200:
try:
item = response['Item']
except KeyError:
return None
return item
Now, your search page should be fully working. Here are some of the examples.
Screenshots of searching for billionaire whose net worth is under 40 Billion and currently lives in New York, NY
Screenshots of searching for billionaire whose net worth is under 51 Billion
Screenshots of searching for billionaire who currently lives in Medina, WA
Full code for urls.py
from django.conf.urls import url
urlpatterns = [
# http://127.0.0.1:8000/search
# http://127.0.0.1:8000/search/
url(r'^search/?', views.search, name='search'),
]
source code hosted on GitHub
Full code for views.py
from django.shortcuts import render
from django.template import loader
from django.http import HttpResponse
from boto3.dynamodb.conditions import Key, Attr
import boto3
import botocore
# dynamodb configuration
dynamodb = boto3.resource(
'dynamodb',
aws_access_key_id='put_your_aws_access_key_here',
aws_secret_access_key='put_your_aws_secret_access_key_here',
region_name='puy_your_amazon_dynamodb_region_here')
# http://127.0.0.1:8000/search
# http://127.0.0.1:8000/search/
def search(request):
template = loader.get_template('search.html')
params = retrieve_all_get_parameters(request)
billionaires = get_list_of_billionaires(params)
context = {
'billionaires' : billionaires
}
return HttpResponse(template.render(context, request))
def get_list_of_billionaires(param):
filter_expression = []
expression_attribute_names = {}
expression_attribute_values = {}
try:
net_worth_under = param['net_worth_under']
filter_expression.append('(#net_worth <= :net_worth_under)')
expression_attribute_names['#net_worth'] = 'net_worth'
expression_attribute_values[':net_worth_under'] = net_worth_under
except KeyError:
pass
try:
current_residence = param['current_residence']
filter_expression.append('(#l = :current_residence)')
expression_attribute_names['#l'] = 'current_location'
expression_attribute_values[':current_residence'] = current_residence
except KeyError:
pass
try:
table = dynamodb.Table('put_your_amazon_dynamodb_table_name_here')
except botocore.exceptions.ClientError as e:
# http://stackoverflow.com/questions/33068055/boto3-python-and-how-to-handle-errors
return 'failed'
else:
filtered_string = filter_expression_to_string(filter_expression)
if filtered_string != '' and expression_attribute_names and expression_attribute_values:
response = table.scan(
FilterExpression = filtered_string,
ExpressionAttributeNames = expression_attribute_names,
ExpressionAttributeValues = expression_attribute_values,
)
else:
response = table.scan(
ReturnConsumedCapacity = 'TOTAL',
)
if response['ResponseMetadata']['HTTPStatusCode'] == 200:
try:
item = response['Item']
except KeyError:
return None
return item
def retrieve_all_get_parameters(request):
param = {}
net_worth_under = request.GET.get('net_worth_under')
current_residence = request.GET.get('current_residence')
if net_worth_under != None and net_worth_under != '':
param['net_worth_under'] = net_worth_under
if current_residence != None and current_residence != '':
param['current_residence'] = current_residence
return param
def filter_expression_to_string(filter_expression):
if not filter_expression:
return ''
length_of_filter_expression = len(filter_expression)
filter_expression_to_string = ''
for x in range(length_of_filter_expression):
filter_expression_to_string += filter_expression[x]
if x + 1 != length_of_filter_expression:
filter_expression_to_string += ' and '
return filter_expression_to_string
source code hosted on GitHub
Full code for search.html
{% for billionaire in billionaires %}
{{ billionaire.first_name }} {{ billionaire.last_name }}
{{ billionaire.source_of_wealth }}
{{ billionaire.current_location }}
{{ billionaire.net_worth }}
{% endfor %}
source code hosted on GitHub
Wrapping Up
Hopefully this guide has given you the confidence to do the scan operation in Amazon Dynamodb and see how it works with the Django under the hood. I hope that this tutorial has helped you and thank you for reading!
Resources
I’ll try to keep this list current and up to date. If you know of a great resource you’d like to share or notice a broken link, please let us know.
Getting started
- Django
- Introduction of Amazon Dynamodb
- Amazon Dynamodb
- Learning React.js: Getting Started and Concepts
- React
- Source code of React
Comments