<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: John Shodipo</title>
    <description>The latest articles on DEV Community by John Shodipo (@johnkayode).</description>
    <link>https://dev.to/johnkayode</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F532405%2F9975b047-9f4e-482c-be23-5708870c6bb7.png</url>
      <title>DEV Community: John Shodipo</title>
      <link>https://dev.to/johnkayode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/johnkayode"/>
    <language>en</language>
    <item>
      <title>How Things Work: Shazam</title>
      <dc:creator>John Shodipo</dc:creator>
      <pubDate>Thu, 21 Sep 2023 13:37:06 +0000</pubDate>
      <link>https://dev.to/johnkayode/how-things-work-shazam-2ka4</link>
      <guid>https://dev.to/johnkayode/how-things-work-shazam-2ka4</guid>
      <description>&lt;h2&gt;
  
  
  How Things Work
&lt;/h2&gt;

&lt;p&gt;This marks the start of what I hope will be an ongoing series. Throughout this series, we will explore the inner workings of various technologies. While we won't delve too deeply into technical details, the goal is to provide an accessible and inquisitive understanding of how things function&lt;/p&gt;

&lt;h2&gt;
  
  
  Shazam
&lt;/h2&gt;

&lt;p&gt;Have you ever found yourself humming along to a catchy tune without being able to put a name to the song? That's where music recognition apps come to the rescue, and one of the most renowned ones is Shazam. Shazam can identify music based on a short sample captured through the device's microphone. It typically takes about 5 seconds to locate a match within its extensive music database, even in noisy environments.&lt;/p&gt;

&lt;p&gt;Originally created by London-based Shazam Entertainment, Shazam gained further prominence when it was acquired by Apple Inc. in 2018 for a reported sum of about $400 million.&lt;/p&gt;

&lt;h2&gt;
  
  
  How It Works
&lt;/h2&gt;

&lt;p&gt;Shazam is reported to have around 11 million tracks stored in its database, and yet, it takes only a few seconds to find a match. How does this happen?&lt;/p&gt;

&lt;p&gt;Shazam creates an audio fingerprint for all audio files in its database and also creates one for the sample audio to be compared. What is an audio fingerprint? It is a compact representation of an audio by extracting relevant features and patterns of the audio content that makes it distinct.&lt;/p&gt;

&lt;p&gt;The fingerprint of the sample audio is compared against the fingerprints of all the audio files in the database to find matches. It is possible for two audio files to have similar fingerprints but be different, so each match is evaluated for correctness.&lt;/p&gt;

&lt;p&gt;There are some guiding principles for creating these fingerprints.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Temporary Locality&lt;/strong&gt;: The fingerprint should only consider the music that's happening around a specific moment in time. It shouldn't be influenced by far-off parts of the music. 
Imagine you're listening to a song, and you want to identify a particular saxophone solo in that song. Temporal locality in this context means that you're pinpointing the exact moment in the song when the saxophone solo starts and ends. You're not concerned with events happening far before or after the solo; you're specifically interested in that solo's duration and the sounds within that time frame.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Translation-Invariance&lt;/strong&gt;: If the same audio appears in different parts of a file, the fingerprint should still be the same. It doesn't matter where in the file that music is. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Robustness&lt;/strong&gt;: Even if the audio file is a bit messed up or low quality, the fingerprint should still work. It should be able to handle copies of the music that aren't perfect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Entropy&lt;/strong&gt;: The fingerprint should be complex enough to minimize the chance of falsely thinking two pieces of music are the same when they're not.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A spectrogram, which is a visual representation of audio, is created, able to represent time, frequency, and amplitude all on one graph. To make the audio recognition algorithm work well with noise and distortions, certain points on the spectrogram with higher amplitude (loudness) than nearby points are selected. The spectrogram is then reduced to a set of coordinates called a constellation map, removing the amplitude information but retaining the essential features.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--VaYFCjS8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0vvh5pv71lwd43gga6z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--VaYFCjS8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/v0vvh5pv71lwd43gga6z.png" alt="Constellation Map" width="420" height="338"&gt;&lt;/a&gt; &lt;em&gt;credits: &lt;a href="http://insidebigdata.com"&gt;http://insidebigdata.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;To match a piece of audio, the constellation map of that audio is overlaid on the map of a song in the database. At the points where the dots align, a match has been found. This method allows for the rapid identification of a small set of points in a vast database, even in the presence of noise or when some audio features are missing.&lt;/p&gt;

&lt;p&gt;However, direct matching from constellation maps can be slow. Therefore, researchers have developed a faster method using combinatorial hashing. Fingerprint hashes are generated from the constellation map by pairing up time-frequency points in a specific way. For a given point (anchor point) in the audio signal, other points (target points) ahead in time are selected, typically within a limited time window. These selected pairs create a combinatorial association of features. &lt;/p&gt;

&lt;p&gt;The frequency values of these pairs are mapped to 10-bit integers, while the time differences are encoded as 12-bit integers. This encoding results in a single 32-bit integer hash for each pair. These hashes effectively summarize the audio content within the paired segments.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--BydPCN-7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4t68g6usqnhhz5tkxon.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--BydPCN-7--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_800/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w4t68g6usqnhhz5tkxon.png" alt="Combinatorial Hashing" width="616" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once these hashes are generated, they can be used to search and identify audio content in a database. The hashes, along with associated metadata such as the track ID and the time of occurrence, are stored in a database for each known audio track. Each audio track is represented by a set of these hash values in the database.&lt;/p&gt;

&lt;p&gt;To find a match for an audio sample, the same process of generating hashes is followed for the sample. The generated hashes are compared against the hashes stored in the database. This comparison is done efficiently because the hash values are 32-bit integers, which are computationally lightweight to compare. A match is identified when there's a significant number of matching query hashes that align well in terms of time with the hashes in a specific audio track in the database. This alignment indicates a likely match between the query and that particular audio track. To confirm the match, the associated time offsets and track IDs of the matching hashes are examined. A high concentration of matching hashes at similar time offsets and track IDs confirms the correctness of the match.&lt;/p&gt;

&lt;p&gt;Shazam has evolved since it last revealed its audio search algorithm, but we've provided a basic understanding of how it works, encompassing concepts like audio fingerprints and combinatorial hashing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;br&gt;
&lt;a href="https://en.wikipedia.org/wiki/Shazam_(application)"&gt;https://en.wikipedia.org/wiki/Shazam_(application)&lt;/a&gt;&lt;br&gt;
&lt;a href="https://www.ee.columbia.edu/~dpwe/papers/Wang03-shazam.pdf"&gt;https://www.ee.columbia.edu/~dpwe/papers/Wang03-shazam.pdf&lt;/a&gt;&lt;/p&gt;

</description>
      <category>softwareengineering</category>
      <category>audio</category>
    </item>
    <item>
      <title>Building a Wallet System with Django and Wallets Africa API</title>
      <dc:creator>John Shodipo</dc:creator>
      <pubDate>Thu, 28 Oct 2021 07:27:29 +0000</pubDate>
      <link>https://dev.to/johnkayode/building-a-wallet-system-with-django-and-wallets-africa-api-2l2g</link>
      <guid>https://dev.to/johnkayode/building-a-wallet-system-with-django-and-wallets-africa-api-2l2g</guid>
      <description>&lt;h2&gt;
  
  
  Overview
&lt;/h2&gt;

&lt;p&gt;Django is a Python framework for rapid web development. It takes care of much of the hassle of web development, so you can focus on writing your app without needing to reinvent the wheel. &lt;a href="https://wallets.africa/" rel="noopener noreferrer"&gt;Wallets Africa&lt;/a&gt;  helps Africans and African owned businesses send money, receive money, make card payments and access loans.&lt;/p&gt;

&lt;p&gt;A good number of applications today use digital wallets to enable users pay for services like electricity, tickets or even transfer money. Wallets Africa API makes it easy for developers to manage users' wallets and enable users to receive and withdraw money on your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Let's create and activate a virtual environment for our project. A virtual environment helps to keep our project dependencies isolated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;MacOS/Linux&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

python -m venv env
source env/bin/activate


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Windows&lt;/strong&gt;&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

python -m venv env
env\scripts\activate


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should see the name of your virtual environment (env) in brackets on your terminal line.&lt;/p&gt;

&lt;p&gt;Next, we install django and create a django project&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

pip install django
django-admin startproject django_wallets


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;and change your directory to the project folder&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

cd django_wallets


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We'd be having two applications in this project.&lt;br&gt;
An accounts app to handle user authentication and authorization, then a wallets app to handle deposits and withdrawals for each user.&lt;br&gt;
Let's create our accounts and wallets applications:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

python manage.py startapp accounts &amp;amp;&amp;amp; python manage.py startapp wallets


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This will create two folders; &lt;em&gt;accounts&lt;/em&gt; and &lt;em&gt;wallets&lt;/em&gt; in our project folder. Now, we need to register our apps with the project. Open the settings file in our &lt;em&gt;django_wallets&lt;/em&gt; folder and find the &lt;strong&gt;INSTALLED APPS&lt;/strong&gt; section, you should find this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Add the newly created apps by replacing it with this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'accounts.apps.AccountsConfig',
    'wallets.apps.WalletsConfig'
]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now let's build our accounts application. By default, Django uses usernames to unique identify users during authentication. In this project however, we'd use emails instead. To do this, we'd create a custom user model by subclassing Django's &lt;strong&gt;AbstractUser&lt;/strong&gt; model. First, we create a &lt;strong&gt;managers.py&lt;/strong&gt; file in the &lt;strong&gt;accounts&lt;/strong&gt; folder for our CustomUser Manager:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from django.contrib.auth.base_user import BaseUserManager
from django.utils.translation import gettext_lazy as _

class CustomUserManager(BaseUserManager):
    def create_user(self, email, password, **extra_fields):
        if not email:
            raise ValueError(_("email address cannot be left empty!"))
        email = self.normalize_email(email)
        user = self.model(email=email, **extra_fields)
        user.set_password(password)
        user.save()
        return user

    def create_superuser(self, email, password, **extra_fields):
        extra_fields.setdefault("is_staff", True)
        extra_fields.setdefault("is_superuser", True)
        extra_fields.setdefault("is_active", True)
        extra_fields.setdefault("user_type", 'ADMIN')

        if extra_fields.get("is_staff") is not True:
            raise ValueError(_("superuser must set is_staff to True"))
        if extra_fields.get("is_superuser") is not True:
            raise ValueError(_("superuser must set is_superuser to True"))

        return self.create_user(email, password, **extra_fields)



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A Manager is the interface through which database query operations are provided to Django models. By default, Django adds a Manager with the name &lt;strong&gt;objects&lt;/strong&gt; to every Django model class. We would be overriding this custom User Manager with this CustomUserManager which uses email as the primary identifier instead.&lt;/p&gt;

&lt;p&gt;Next, We'd create our custom user model:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from django.contrib.auth.models import AbstractUser
from django.db import models
from django.utils.translation import gettext_lazy as _

from .manager import CustomUserManager

import uuid


class CustomUser(AbstractUser):

    username = None
    uid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    email = models.EmailField(_("email address"), blank=False, unique=True)
    first_name = models.CharField(_("first name"), max_length=150, blank=False)
    last_name = models.CharField(_("last name"), max_length=150, blank=False)
    date_of_birth = models.DateField(_("date of birth"), max_length=150, blank=False)
    verified = models.BooleanField(_("verified"), default=False)


    USERNAME_FIELD = "email"
    REQUIRED_FIELDS = []

    objects = CustomUserManager()

    def __str__(self):
        return self.email


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Here, we removed the &lt;strong&gt;username&lt;/strong&gt; field and made &lt;strong&gt;email&lt;/strong&gt; field unique and, then set the email as the &lt;strong&gt;USERNAME_FIELD&lt;/strong&gt;, which defines the unique identifier for the User model. We also used a UUID_FIELD as our unique identifier and used the Python's &lt;strong&gt;uuid&lt;/strong&gt; library to generate random objects as default values.&lt;/p&gt;

&lt;p&gt;Then, we add this to our &lt;strong&gt;settings.py&lt;/strong&gt; file so Django recognize the new User model:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

AUTH_USER_MODEL = "accounts.CustomUser"


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, let's migrate our database (We'd be using the default sqlite database for the purpose of this tutorial)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

python manage.py makemigrations &amp;amp;&amp;amp; python manage.py migrate


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now let's run our application&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

python manage.py runserver


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Open &lt;a href="http://127.0.0.1:8000/" rel="noopener noreferrer"&gt;http://127.0.0.1:8000/&lt;/a&gt; on your browser and the response should be similar as the image below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1634942421036%2FSwX5knwpx.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1634942421036%2FSwX5knwpx.jpeg" alt="django.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let's create our registration and login forms. Create a &lt;strong&gt;forms.py&lt;/strong&gt; file in the &lt;strong&gt;accounts&lt;/strong&gt; folder and copy this:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from django import forms
from django.forms.widgets import PasswordInput, TextInput, EmailInput, FileInput, NumberInput
from .models import CustomUser


from .models import CustomUser




class UserRegistrationForm(forms.ModelForm):
    password1 = forms.CharField(widget=PasswordInput(attrs={'class':'form-control', 'placeholder':'Password', 'required':'required'}))
    password2 = forms.CharField(widget=PasswordInput(attrs={'class':'form-control', 'placeholder':'Confirm Password', 'required':'required'}))

    class Meta:
        model = CustomUser
        fields = ('first_name','last_name','email','date_of_birth') 
        widgets = {
        'first_name':TextInput(attrs={'class':'form-control', 'placeholder':'First Name', 'required':'required'}),
        'last_name':TextInput(attrs={'class':'form-control', 'placeholder':'Last Name', 'required':'required'}),
        'email': EmailInput(attrs={'class':'form-control', 'placeholder':'Email', 'required':'required'}),
        'date_of_birth': DateInput(attrs={'class':'form-control', 'placeholder':'Date of Birth', 'required':'required','type': 'date'}),
    }

    def clean_password2(self):
        password1 = self.cleaned_data.get("password1")
        password2 = self.cleaned_data.get("password2")
        if password1 and password2 and password1 != password2:
            raise forms.ValidationError("Passwords don't match")
        return password2

    def save(self, commit=True):
        user = super().save(commit=False)
        user.set_password(self.cleaned_data["password1"])
        if commit:
            user.save()
        return user

class CustomAuthForm(forms.Form): 
    email = forms.CharField(widget=EmailInput(attrs={'class':'form-control', 'placeholder':'Email', 'required':'required'}))
    password = forms.CharField(widget=PasswordInput(attrs={'class':'form-control','placeholder':'Password', 'required':'required'}))



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should notice the custom CSS classes added to form fields. We'd be using Bootstrap to style the forms. Bootstrap is a CSS framework directed at responsive, mobile-first front-end web development. Next, we create our registration view:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from django.shortcuts import render

from .forms import UserCreationForm



def register(request):
    form = UserRegistrationForm(request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            new_user = form.save()
            return redirect('accounts:register')
    return render(request, "accounts/register.html", context = {"form":form})


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We have created a templates folder in our project directory. You can copy the html templates from Github &lt;a href="https://github.com/Johnkayode/django-wallets/tree/main/templates/" rel="noopener noreferrer"&gt;here&lt;/a&gt;. Go to the &lt;strong&gt;settings.py&lt;/strong&gt; and update the TEMPLATES section:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [BASE_DIR / 'templates'],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This tells Django to load the templates from the templates folder in the project directory. Add the registration url to the app:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from django.urls import path

from .views import register
app_name = "accounts"

urlpatterns = [
    path('register/', register, name="register"),
]



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then we include the accounts app urls to the project:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from django.contrib import admin
from django.urls import path, include

urlpatterns = [
    path('admin/', admin.site.urls),
    path('account/', include('accounts.urls', namespace='accounts'))
]



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Open 127.0.0.1:8000/account/register/ on your browser, this should show up:&lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1635262350422%2FSBu7JjIX-.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1635262350422%2FSBu7JjIX-.jpeg" alt="login.jpg"&gt;&lt;/a&gt;&lt;br&gt;
Users can now register. Now let's create our login view:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

def login_user(request):
    form = CustomAuthForm(request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            cd = form.cleaned_data
            user = authenticate(request, email = cd['email'], password=cd['password']) 
            if user is not None:
                login(request, user)
                return redirect('accounts:dashboard')
            else:
                messages.error(request, 'Account does not exist')
    return render(request, "accounts/login.html", context = {"form":form})

@login_required
def dashboard(request):
    return render(request, "dashboard.html", context={})


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then, we add the urls:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

urlpatterns = [
    path('register/', register, name="register"),
    path('login/', login_user, name="login"),
    path('', dashboard, name="dashboard"),
]



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, our login and registration routes should be working. After successful login, the user should be redirected to the dashboard. You might have noticed the verified field on the CustomUser model is set to False by default. After the user have provided their bvn and a wallet has been created, the verified field is changed to True. But before then, let's update our register route to redirect to login after successful registration:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

def register(request):
    form = UserRegistrationForm(request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            new_user = form.save()
            messages.success(request, 'Account succesfully created. You can now login')
            return redirect('accounts:login')
    return render(request, "accounts/register.html", context = {"form":form})


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let's create our Wallet model:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from django.db import models, transaction
from django.utils.translation import gettext_lazy as _
from accounts.models import CustomUser

import uuid

class Wallet(models.Model):
    uid = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.OneToOneField(CustomUser, on_delete=models.SET_NULL, null=True)
    balance = models.DecimalField(_("balance"), max_digits=100, decimal_places=2)
    account_name = models.CharField(_("account name"), max_length=250)
    account_number = models.CharField(_("account number"), max_length=100)
    bank = models.CharField(_("bank"), max_length=100)
    phone_number = models.CharField(_("phone number"), max_length=15)
    password = models.CharField(_("password"), max_length=200)
    created = models.DateTimeField(auto_now_add=True)



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then, run migrations for the application. The user field's on_delete is set to null because we don't want to delete a wallet even after a user's account has been deleted. A user can only have a wallet after he has been verified. Now let's create our wallet_creation form and view.&lt;br&gt;
&lt;strong&gt;Form&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class BVNForm(forms.Form): 
    bvn = forms.CharField(widget=NumberInput(attrs={'class':'form-control', 'placeholder':'Your BVN', 'required':'required'}))



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;View&lt;/strong&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from wallets.api import WalletsClient
from wallets.models import Wallet

from cryptography.fernet import Fernet



wallet = WalletsClient(secret_key="hfucj5jatq8h", public_key="uvjqzm5xl6bw")
fernet = Fernet(settings.ENCRYPTION_KEY)


@login_required
def create_wallet(request):
    form = BVNForm(request.POST or None)
    if request.method == 'POST':
        if form.is_valid():
            cd = form.cleaned_data
            user = request.user
            bvn = cd["bvn"]
            new_wallet = wallet.create_user_wallet(
                    first_name= user.first_name,
                    last_name= user.last_name,
                    email=user.email,
                    date_of_birth= user.date_of_birth.strftime('%Y-%m-%d'),
                    bvn= str(bvn)
                )
            if new_wallet["response"]["responseCode"] == '200':
                user.verified = True
                user.save()
                Wallet.objects.create(
                    user = user,
                    balance = new_wallet["data"]["availableBalance"],
                    account_name = new_wallet["data"]["accountName"],
                    account_number = new_wallet["data"]["accountNumber"],
                    bank = new_wallet["data"]["bank"],
                    phone_number = new_wallet["data"]["phoneNumber"],
                    password = fernet.encrypt(new_wallet["data"]["password"].encode())
                )
                messages.success(request, "Account verified, wallet successfully created")
                return redirect("accounts:dashboard")
            else:
                messages.error(request, new_wallet["response"]["message"])

    return render(request, "accounts/bvn.html", context = {"form":form})


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I have written a simple API wrapper for Wallets Africa API, you can check it out on  &lt;a href="https://github.com/Johnkayode/django-wallets/blob/main/wallets/api.py" rel="noopener noreferrer"&gt;Github&lt;/a&gt;. For the purpose of this tutorial, we used a test keys and token provided by Wallets Africa, you need to create a Wallets Africa account for your secret and public keys for production:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1635370091714%2FsLZP84bsQ.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1635370091714%2FsLZP84bsQ.png" alt="wallets.png"&gt;&lt;/a&gt;&lt;br&gt;
The &lt;strong&gt;create_wallet&lt;/strong&gt; view receives the BVN and creates the wallet using the API and then saves the wallet details to the database. We used the cryptography package to encrypt the wallet password before saving to the database. Add an &lt;strong&gt;ENCRYPTION_KEY&lt;/strong&gt; to your &lt;strong&gt;settings.py&lt;/strong&gt;, you can also generate the encryption key with the &lt;strong&gt;cryptography&lt;/strong&gt; package:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from cryptography.fernet import Fernet

key = Fernet.generate_key()
ENCRYPTION_KEY = key


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, let's add a permission that prevents unverified users from accessing the dashboard. Create a &lt;strong&gt;decorators.py&lt;/strong&gt; file in the &lt;strong&gt;accounts&lt;/strong&gt; folder:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from functools import wraps
from django.shortcuts import redirect
from django.contrib import messages

def verified(function):
  @wraps(function)
  def wrap(request, *args, **kwargs):

        if request.user.verified:
             return function(request, *args, **kwargs)
        else:
            messages.error(request, "Your account hasn't been verified")
            return redirect("accounts:verify")

  return wrap


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This is a custom decorator that redirects a user to the verification page if the account hasn't been verified. We can now add our custom decorator to the dashboard view:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from .decorators import verified

@login_required
@verified
def dashboard(request):
    wallet = get_object_or_404(Wallet, user=request.user)
    return render(request, "dashboard.html", context={"wallet":wallet})


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;I also added the user's wallet to be rendered on the dashboard. Visit 127.0.0.1:8000/account on your browser, it should redirect you to the verification page if you're unverified or to the dashboard if you are vefiried. The dashboard page should be similar to this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1635295116609%2F1YUD41Wet.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1635295116609%2F1YUD41Wet.jpeg" alt="dashboard.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's add our logout view:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

@login_required
def logout_user(request):
    logout(request)
    return redirect("accounts:login")


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Then, add the logout url:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

urlpatterns = [
    ...
    path('logout/', logout_user, name="logout"),

]



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Now, users can fund their wallets by making a bank transfer to the account linked to their wallets. We need to update their wallet balance as soon as the transfer is successful. This can be done through webhooks. A webhook is a URL on your server where payloads are sent from a third party service (Wallets Africa in this case) whenever certain transaction actions occur on each wallets. First, we create a WalletTransaction model to save each transactions:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

class WalletTransaction(models.Model):
    class STATUS(models.TextChoices):
        PENDING = 'pending', _('Pending')
        SUCCESS = 'success', _('Success')
        FAIL = 'fail', _('Fail')

    class TransactionType(models.TextChoices):
        BANK_TRANSFER_FUNDING = 'funding', _('Bank Transfer Funding')
        BANK_TRANSFER_PAYOUT = 'payout', _('Bank Transfer Payout')
        DEBIT_USER_WALLET = 'debit user wallet', _('Debit User Wallet')
        CREDIT_USER_WALLET = 'credit user wallet', _('Credit User Wallet')

    transaction_id = models.CharField(_("transaction id"), max_length=250)
    status = models.CharField(max_length=200, null=True, 
        choices=STATUS.choices, 
        default=STATUS.PENDING
    )
    transaction_type = models.CharField(max_length=200, null=True,
        choices=TransactionType.choices
        )
    wallet = models.ForeignKey(Wallet, on_delete=models.SET_NULL, 
        null=True
    )
    amount = models.DecimalField(_("amount"), max_digits=100, decimal_places=2)
    date = models.CharField(_("date"), max_length=200)



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We are saving the date in string data because of the uncertain data type in the payload.&lt;br&gt;
Next, we create the view that will consume the webhook:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from django.db import transaction
from django.http import HttpResponse, HttpResponseForbidden
from django.shortcuts import render, get_object_or_404
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_POST

from ipaddress import ip_address, ip_network
import json

from .models import Wallet, WalletTransaction


@csrf_exempt
@require_POST
def webhook(request):
    whitelist_ip = "18.158.59.198"
    forwarded_for = u'{}'.format(request.META.get('HTTP_X_FORWARDED_FOR'))
    client_ip_address = ip_address(forwarded_for)

    if client_ip_address != ip_network(whitelist_ip):
        return HttpResponseForbidden('Permission denied.')

    payload = json.loads(request.body)

    if payload['EventType'] == "BankTransferFunding":
        wallet = get_object_or_404(Wallet, phone_number = payload["phoneNumber"])
        wallet.balance += payload["amount"]
        wallet.save()
        transaction =  WalletTransaction.objects.create(
            transaction_id = payload["transactionRef"],
            transaction_type = "funding",
            wallet = wallet,
            status = "success",
            amount = payload["amount"],
            date = payload["DateCredited"]
        )
    else:
        pass
    return HttpResponse(status=200)




&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This view checks if the webhook is from a trusted IP address (All Wallets Africa webhook comes from the host IP: 18.158.59.198) then updates the wallet balance and also create a wallet transaction. Let's add the webhook to our app urls:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

from django.urls import path

from .views import webhook

urlpatterns = [
    path(
        "webhooks/wallets_africa/aDshFhJjmIalgxCmXSj/",
         webhook,
         name = "webhook"
    ),
]


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We added a random string to the url for a bit of security , add the webhook url to your Wallets Africa dashboard:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1635374175605%2FtcuBCl8Lm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1635374175605%2FtcuBCl8Lm.png" alt="webhook.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Our wallets app is now ready:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1635378673271%2F9NBvdfOdc.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fcdn.hashnode.com%2Fres%2Fhashnode%2Fimage%2Fupload%2Fv1635378673271%2F9NBvdfOdc.jpeg" alt="ready.jpg"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;By integrating Wallets Africa with Django, we built a wallet application that allows user to fund their digital wallets by making a bank transfer. We also went through Django's Custom User Manager features that allows us use emails rather than usernames for authentication.&lt;/p&gt;

&lt;p&gt;The source code is available on  &lt;a href="https://github.com/johnkayode/django-wallets" rel="noopener noreferrer"&gt;Github&lt;/a&gt;.&lt;br&gt;
If you have any questions, don't hesitate to contact me on  &lt;a href="https://twitter.com/nerd_thejohn" rel="noopener noreferrer"&gt;Twitter&lt;/a&gt; .&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
