Welcome to part 2 of this series. Here we'll be writing most of the logic for our application.
If you come across this part first, you can check out part 1 here. We already handled our system and project setup in that tutorial.
Let's get started!
We have three objectives here;
I) Fetch our API data from CoinDesk based on a default 10days range (today - (today-10days))
II) Fetch our API data from CoinDesk based on date range specified by user.
III) Render the fetched data in graphical format using ChartJs for both scenarios mentioned above.
Note: Please ensure you do not mess with the indentation in the views.py
file. Bad indentation might/will not make your code work. Thank you
Objective I
views
First we get our API data and render it out in our html. We'll be editing the content of views.py
file in the price application folder. So it eventually looks like this 👇
import requests
from django.shortcuts import render
from datetime import date, timedelta
#### Create your views here.
def chart(request):
datetime_today = date.today() # get current date
date_today = str(datetime_today) # convert datetime class to string
date_10daysago = str(datetime_today - timedelta(days=10)) # get date of today -10 days
api= 'https://api.coindesk.com/v1/bpi/historical/close.json?start=' + date_10daysago + '&end=' + date_today + '&index=[USD]'
try:
response = requests.get(api, timeout=2) # get api response data from coindesk based on date range supplied by user
response.raise_for_status() # raise error if HTTP request returned an unsuccessful status code.
prices = response.json() #convert response to json format
btc_price_range=prices.get("bpi") # filter prices based on "bpi" values only
except requests.exceptions.ConnectionError as errc: #raise error if connection fails
raise ConnectionError(errc)
except requests.exceptions.Timeout as errt: # raise error if the request gets timed out without receiving a single byte
raise TimeoutError(errt)
except requests.exceptions.HTTPError as err: # raise a general error if the above named errors are not triggered
raise SystemExit(err)
context = {
'price':btc_price_range
}
return render(request, 'chart.html', context)
In code above, we get the current date and the date as at 10 days ago. They'll be in time delta format so we have to convert them to string. Then we concatenate the api string with the date strings. After that, we request for the API data from coindesk with the requests.get() function with the timeout set to 2 seconds. You can change the timeout to whatever suits you. You can read more about timeouts here
{"bpi":{"2021-08-08":43804.8083,"2021-08-
09":46283.2333,"2021-08-10":45606.6133,"2021-08-
11":45556.0133,"2021-08-12":44415.8567,"2021-08-
13":47837.6783,"2021-08-14":47098.2633,"2021-08-
15":47018.9017,"2021-08-16":45927.405,"2021-08-
17":44686.3333},"disclaimer":"This data was produced from
the CoinDesk Bitcoin Price Index. BPI value data returned
as USD.","time":{"updated":"Aug 18, 2021 00:03:00
UTC","updatedISO":"2021-08-18T00:03:00+00:00"}}
Next, we convert the received response above to JSON format and then filter out only the bpi dictionary which contains the data (dates and prices) that we need. If the request fails, we handle the various errors which might occur such as timeout, connection and http errors. Then we pass the variable to the context dictionary which is rendered with our templates. We also change the template name from base.html to chart.html which is located in the template folder in our price directory.
base.html
Change the content of your base.html file to this
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
{% block style %}
{% endblock style %}
<title>{% block title %} {% endblock title %}</title>
</head>
<body>
<div class="">
{% block content %}
{% endblock content %}
</div>
{% block script %}
{% endblock script %}
</body>
</html>
chart.html
Add this to your empty chart.html file
{% extends "base.html" %}
{% block style %}
{% endblock style %}
{% block title %}
Bitcoin Price Chart
{% endblock title %}
{% block content %}
<!-- Filter the chart with the selected dates -->
{% for date,price in price.items %}
<span class="date-item">{{date}} </span> |
<span class="price-item">{{price}}</span>
<br>
{% endfor %}
{% endblock content %}
{% block script %}
{% endblock script %}
Install the requests imported in our views.py
file with the command below
pip install requests
Then you can start your server to ensure that everything is working properly
python manage.py runserver
open this url 127.0.0.1:8000 in your browser.
You should see see this on your webpage now.
Objective II
We need to create a form which the user will use to select their preferred date range. Therefore we have to create a forms.py
file in our price directory. Then we put this code in it to create the date inputs for the user form.
forms
from django import forms
class PriceSearchForm(forms.Form):
date_from = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}))
date_to = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}))
views
Next we have to import the form in our views.py
file. Add this below the import settings lines at the top of the page on line 4.
from .forms import PriceSearchForm
Then we add this line of code to fetch the form data, check for form errors and also use the date range submitted by the user to make a custom API request from CoinDesk.
date_from = None
date_to = None
wrong_input = None
search_form= PriceSearchForm(request.POST or None) #get post request from the front end
if request.method == 'POST':
if search_form.is_valid(): #Confirm if valid data was received from the form
date_from = request.POST.get('date_from') #extract input 1 from submitted data
date_to = request.POST.get('date_to') #extract input 2 from submitted data
else:
raise Http400("Sorry, this did not work. Invalid input")
api= 'https://api.coindesk.com/v1/bpi/historical/close.json?start=' + date_from + '&end=' + date_to + '&index=[USD]' #use the 10days period obtained above to get default 10days value
if date_to > date_from: #confirm that input2 is greater than input 1
try:
response = requests.get(api, timeout=2) #get api response data from coindesk based on date range supplied by user
response.raise_for_status() #raise error if HTTP request returned an unsuccessful status code.
response = requests.get(api) #get api response data from coindesk based on date range supplied by user
prices = response.json() #convert response to json format
btc_price_range=prices.get("bpi") #filter prices based on "bpi" values only
from_date= date_from
to_date= date_to
except requests.exceptions.ConnectionError as errc: #raise error if connection fails
raise ConnectionError(errc)
except requests.exceptions.Timeout as errt: #raise error if the request gets timed out without receiving a single byte
raise TimeoutError(errt)
except requests.exceptions.HTTPError as err: #raise a general error if the above named errors are not triggered
raise SystemExit(err)
else:
wrong_input = 'Wrong date input selection: date from cant be greater than date to, please try again' #print out an error message if the user chooses a date that is greater than input1's date
#add search form variable to your context file
context{
'price':btc_price_range,
'search_form':search_form,
'wrong_input' : wrong_input
}
We ensure that the user doesnt break the application by supplying a 'date from' that is greater than the 'date to'. If this happens an error message will be displayed to the user.
chart.html
Our content should be placed within the block content tags. Add your error alert code plus the created form and the CSRF token template tag to protect your application against attacks. You can read more about protection against cross site forgeries here
<!-- error with selected dates -->
{% if wrong_input %}
<div class="alert alert-warning" role="alert">
{{wrong_input}}
</div>
{% endif %}
<form id="myForm" action="" method='POST'>
{% csrf_token %}
{{search_form}}
<div class="">
<button type="submit" class="btn btn-primary mt-3"> Render</button>
</div>
</form>
Pick any date range to test the current state of our application. You should have control over the dates and prices being displayed on your web page now.
Objective III
So far we have been able to write the logic which enables the user input to be passed across to our API request and we have also been able to communicate with the API successfully. Now it's time to display the data (dates and prices) on our webpage in graphical format. We'll be using chart.js to achieve this
chart.html
First we add the canvas element inside the block element tags in chart.html file
<div class="chart-container">
<canvas id="btcChart"></canvas>
</div>
We also need to add the CDN link for chart.js
and a link to our javascript file named chart.js
inside the block script tags
<!-- Chartjs CDN link -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@3.5.1/dist/chart.min.js"></script>
<!-- Chart javascript file -->
<script src="{% static 'js/chart.js' %}"></script>
At the moment your chart.html file should look exactly like this 👇 The {load static} template tag included on line 2 of chart.html, generates the absolute URL of our static files (css and javascript files).
chart
Now create a chart.js
file in the static/js directory and add this code.
var dates = document.getElementsByClassName('date-item')
var prices = document.getElementsByClassName('price-item')
//convert html collection to array
const date=[]
const price=[]
for (let i = 0; i < dates.length; i++) { //iterate over the html collection (hidden input) retrieved from the html
date[i] = dates[i].innerHTML //get the value(date) of each of the html collection (hidden input)
console.log(date[i])
}
for (let j = 0; j < prices.length; j++) { //iterate over the html collection (hidden input) retrieved from the html
price[j] = prices[j].innerHTML //get the value(prices) of each of the html collection (hidden input)
}
// Chart js code
var context = document.getElementById('btcChart').getContext('2d');
new Chart(context, {
type: 'line',
data: {
labels: date, //make the values of the date array the labels for the bar chart
datasets: [{
label: 'Price fluctuation',
data: price, //make the values of the price array the data for the bar chart
backgroundColor: [
'rgba(255, 99, 132, 0.2)',
'rgba(54, 162, 235, 0.2)',
'rgba(255, 206, 86, 0.2)',
'rgba(75, 192, 192, 0.2)',
'rgba(153, 102, 255, 0.2)',
'rgba(255, 159, 64, 0.2)'
],
borderColor: [
'rgba(255, 99, 132, 1)',
'rgba(54, 162, 235, 1)',
'rgba(255, 206, 86, 1)',
'rgba(75, 192, 192, 1)',
'rgba(153, 102, 255, 1)',
'rgba(255, 159, 64, 1)'
],
borderWidth: 3
}]
},
options: {
responsive: true,
plugins: {
title: {
display: true,
text: 'Bitcoin Price Change'
},
},
scales: {
x: {
display: true,
title: {
display: true,
text: 'Date'
}
},
y: {
display: true,
title: {
display: true,
text: 'Price in USD$'
}
}
}
}
});
We get the HTML elements using the HTML DOM Document; you can read up on it here , then we convert the content of the html collection to an array and add the arrays to the chart js code below it. The date array containing the dates is made to appear on the X-axis while the price array will appear on the Y-axis. You can choose any format to represent your data; bar chart, line chart , pie chart e.t.c. You can explore chart.js and play around with your configurations.
Congratulations. We have come to the end of Part 2 of the series. In this tutorial, we were able to successfully consume CoinDesk's API, manipulate the API get request with desired input and also render the data out both as pure JSON and in graphical format using chart.js
.
At the moment our application looks like this; In part 3 of this series. Our objectives would be to; I) Carry out seperation of concerns. II) Add styling to our page to make the User Interface clean.
Please ensure you check it out as well before accessing the repo.
Github repo :source code.
If you have any question, feel free to drop it as a comment or send me a message on Linkedin or Twitter and I'll ensure i respond as quick as i can.