Consuming API with Django and Chart.js [Part 2]

Consuming API with Django and Chart.js [Part 2]

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. API Json data

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. Error Picture

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.
Example

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 👇 Chart.html page 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; Project 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.