In the CSM team at Pixalate, we needed something to bring all our tools together, we used Salesforce, Pendo and JIRA mainly. Logging into each one of these platforms was tedious.

There was the talk of the engineers building a portal. So with me wanting to learn Django I stepped in and asked if I could take on the job. So here it is…

Requirements

  • Google Auth sign-on, we used Google tools so we all had a Google login.
  • The ability for users to create client entries as well as edit and delete them from a database
  • Use of JIRA API so we’re able to populate each client entry with their relevant tickets
  • Use of Pendo API so we’re able to track visitor data

The Start

Before I started this project, I didn’t know anything about Django. I knew some Python but my Python framework skills were 0. So I took to Youtube and found the Django Tutorial series from Corey Schafer. Where I learnt a lot of my Python from via his Python Beginner Tutorials . I got to episode 10 before moving away from the tutorials and going it alone.

Google sign-on

I was able to get users to create a profile, allowing them to sign on and start using the dashboard. This wasn’t ideal for us, as anyone who had access to the dashboard could create a user and then sign-on. So I took a look at python-social-auth docs and with this tutorial I was able to get the Google sign-on working. Using the Google OAuth API I was able to lock down the signing into only people with an @pixalate.com email.

This now allowed Pixalate team members to sign in using a simple button, followed with some simple styling.

login

They would then be able to create, edit and delete users. Once logged in…BOOM! Stage one complete!

The Database

Once a user was logged in successfully, they could create, edit and delete entries from the SQLite database that comes with Django. In Part 5 of the tutorial series. Cory went through creating a Model so we were able to create a table in our db.sqlite3 file.

I have of course eddited the Model for my use, I wanted to keep data on the following:

  • Client Name
  • Client ID
  • Client Products
  • Login Creds
  • URLs to dashboard
  • URLs to social sites

Below you can see the Model I created for the database.

class Post(models.Model):
    client_name = models.CharField(max_length=100)
    client_ID = models.CharField(max_length=100)
    client_linkedinurl = models.CharField(max_length=150, default="Linkedin Profile...")
    client_salesforceurl = models.CharField(max_length=150, default="Salesforce Profile...")
    client_qaresults = models.CharField(max_length=100, default="url:")
    client_password = models.CharField(max_length=100, default="***")
    client_products = (
        ('Analytics', 'Analytics Only'),
        ('Datafeeds', 'Datafeeds Only'),
        ('Analytics, Datafeeds', 'Analytics & Datafeeds'),
        ('MRT', 'Media Ratings Tool'),
    )
    client_products = models.CharField(max_length=100, choices=client_products)
    client_tags = models.TextField(default="Client Tags:")

    client_notes = models.TextField(default="Notes Here...")
    client_FTP = models.CharField(max_length=100, default="ftp://")
    client_API_login = models.CharField(max_length=100, default="...")
    client_API_password = models.CharField(max_length=100, default="...")
    date_posted = models.DateTimeField(auto_now=True)
    author = models.ForeignKey(User, on_delete=models.CASCADE)

    def __str__(self):
        return self.client_name

    def get_absolute_url(self):
        return reverse('post-detail', kwargs={'pk': self.pk})

Once I created the database Model and made my migrations, it was time to log in and start creating enteries. I hooked up the client_linkedinurl and client_salesforceurl to relevant icons, that allowed the user to click out to the relevant social sites.

Getting API data

Let’s use the JIRA API has the example here calling an API in Python is pretty dam easy using the requests module. First I created a separate file called api_calls.py this file would hold each API call as a function.

import requests
import json
import os

JIRA_API_KEY = os.environ.get('JIRA_API_KEY')

def jira_data():
    url = 'https://domain.atlassian.net/rest/api/2/search?jql=project=CS'
    r = requests.get(url, auth=(
        '[email protected]', 'JIRA_API_KEY'))
    data = r.json()

    return data

I had the functions pulling the API data as I wanted in my separate file, now to get them to populate in my template. I had to use the render_to_repsonse function in my class to do this.

Using context to fill the data into the template.

Once you have a compiled Template object, you can render a context with it. You can reuse the same template to render it several times with different contexts.

class PostDetailView(DetailView):
    model = Post
    template_name = 'clients/post_detail.html'

    def render_to_response(self, context, **response_kwargs):
        jira_result = api_calls.jira_data()
        client_name = self.get_object().client_name.lower()
        context['jira_data'] = [issue for issue in jira_result['issues']
                                if client_name.lower() in issue['fields']['customfield_10907'][0]['value'].lower()
                                ]

        return super().render_to_response(context, **response_kwargs)

Once I got the data the way I wanted it, it was a simple case of implementing within my template.

 {% for ticket in jira_data %}
<tr>
	<td class="text-center">{{ ticket.key }}</td>
	<td>
		<a href="https://domain.atlassian.net/browse/{{ ticket.key }}"
			>{ ticket.fields.summary }}</a
		>
	</td>
	... 
</tr>

The Bugs

This was all working as intended. Lovely. But there were some bugs. The first bug was the date that was being sent by the API. The second bug, well not really a bug was to filter the response in the views.py and not the template

The date format

The date was being outputted as 2019-09-05T12:16:13.713-0700 I only wanted DD/MM/YY.

I couldn’t just use datetime So off to Stackoverflow I went. The idea was to start creating a custom template filter

You can read more about it in my post here

from django import template
from django.utils import formats
import datetime
register = template.Library()


@register.filter(expects_localtime=True, is_safe=False)
def custom_date(value, arg=None):
    if value in (None, ''):
        return ''

    if isinstance(value, str):
        api_date_format = '%Y-%m-%dT%H:%M:%S.%f%z'  # 2019-08-30T08:22:32.245-0700
        value = datetime.datetime.strptime(value, api_date_format)

    try:
        return formats.date_format(value, arg)
    except AttributeError:
        try:
            return format(value, arg)
        except AttributeError:
            return ''

I had some bugs with api_date_format because the API produced a whole load of extras I checked out the datetstrftime() and strptime() Behavior table and made some edits. Once I got rid of the errors I moved on to the template.

First I had to load the custom filter into my template, then I used the custom_date to change it to what I needed.


<td class="text-center">{{ ticket.fields.updated|custom_date"Y,D,M" }}</td>

Filtering the response in the views.py

I wanted to filter out the API response to display JIRA tickets that were only relevant to the client on that page. So first I had to import the Post model into views.py as I wanted to use the client_name from the model to use for logic. So I added the following to the render_to_response function:

client_name = self.get_object().client_name.lower()

This would allow me to grab the client_name information from the Post model. I then used List Comprehension to filter in views.py instead of my template, in an attempt to speed up the API call.

context['jira_data'] = [issue for issue in jira_result['issues']
                        if client_name.lower() in issue['fields']['customfield_10907'][0]['value'].lower()
                    ]

I used .lower() on to prevent tickets not display due to user input when it came to case sentive strings.