Create Task Pomodoro Timer Using Django
Below is the step-by-step implementation of the Task Pomodoro Timer.
To install Django follow these steps.
Starting the Project Folder
To start the project use this command:
django-admin startproject core
cd core
To start the app use this command
python manage.py startapp home
Now add this app to the ‘settings.py’
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"django.contrib.staticfiles",
"home",
]
Setting Necessary Files
models.py: In this example code defines a Django model named “Timers” with fields such as title, hours, minutes, seconds, category, uuid, and priority. It represents attributes for a Pomodoro Timer, with default values specified for some fields. This model facilitates the storage and management of timer-related information in a Django application.
Python3
from django.db import models # models.py class Timers(models.Model): title = models.CharField(max_length = 100 ) hours = models.IntegerField(default = 0 ) minutes = models.IntegerField(default = 25 ) seconds = models.IntegerField(default = 0 ) category = models.CharField(max_length = 50 ) # Add this line uuid = models.IntegerField(default = 0 ) priority = models.IntegerField(default = 0 ) |
views.py : In this example code defines Django views for a Pomodoro timer web app. It handles user authentication, registration, timer addition, deletion, and displays timers ordered by priority. The ‘pomodoro_timer’ view renders timers and a form, while ‘login’ and ‘signup’ manage user authentication and registration, respectively.
Python3
from django.shortcuts import render, redirect from .models import Timers from django.contrib.auth.models import User from django.contrib import auth from django.contrib import messages from .forms import PomodoroForm from django.db.models import Sum , F, ExpressionWrapper, fields # views.py def pomodoro_timer(request): timers = Timers.objects. all ().order_by( 'priority' ) form = PomodoroForm() if len (timers) = = 0 : return render(request, 'pomodromo_timer.html' , { 'form' : form, 'editable' : False , 'timers' : None , }) return render(request, 'pomodromo_timer.html' , { 'form' : form, 'editable' : False , 'timers' : timers, }) def login(request): if request.method = = "POST" : email = request.POST[ 'email' ] password = request.POST[ 'password' ] if User.objects. filter (username = email).exists(): user = auth.authenticate(username = email, password = password) print (user) if user is not None : auth.login(request, user) return redirect( 'pomodoro_timer' ) else : messages.error(request, 'Invalid credentials' ) return redirect( "login" ) else : messages.info(request, "Invalid email or password" ) return redirect( 'login' ) else : return render(request, 'login.html' ) def signup(request): if request.method = = 'POST' : name = request.POST[ 'username' ] email = request.POST[ 'email' ] password = request.POST[ 'password' ] if User.objects. filter (first_name = name).exists(): messages.info(request, "Username already taken" ) return redirect( 'signup' ) elif User.objects. filter (username = email).exists(): messages.info(request, "Email already taken" ) return redirect( 'signup' ) else : user = User.objects.create_user(first_name = name, username = email, password = password) print (user) print ( "User registered Successfully" ) user.save() return redirect( 'login' ) else : return render(request, 'signup.html' ) def logout(request): auth.logout(request) return redirect( 'pomodoro_timer' ) def add(request, id ): editable = True if request.method = = "POST" : editable = False form = PomodoroForm(request.POST) if form.is_valid(): form_instance = form.save(commit = False ) form_instance.uuid = id form_instance.save() return redirect( "pomodoro_timer" ) else : form = PomodoroForm() timers = Timers.objects. all () return render(request, 'pomodromo_timer.html' , { 'form' : form, "editable" : editable, 'timers' : timers }) def delete(request, id ): timers = Timers.objects.get( id = id ) timers.delete() print ( "Successfully Deleted {{id}}" ) timers = Timers.objects. all () form = PomodoroForm() if len (timers) = = 0 : return render(request, 'pomodromo_timer.html' , { 'form' : form, "editable" : False , 'timers' : None }) return render(request, 'pomodromo_timer.html' , { 'form' : form, "editable" : False , 'timers' : timers }) |
forms.py : This Django form, named `PomodoroForm`, is associated with the `Timers` model. It includes fields for ‘title,’ ‘hours,’ ‘minutes,’ ‘seconds,’ ‘category,’ and ‘priority.’ The form defines widget attributes to enforce required input and validates that hours, minutes, and seconds are non-negative in the `clean` method, raising a validation error if any are negative.
Python3
from django import forms from .models import Timers class PomodoroForm(forms.ModelForm): class Meta: model = Timers fields = [ 'title' , 'hours' , 'minutes' , 'seconds' , 'category' , 'priority' ] widgets = { 'title' : forms.TextInput(attrs = { 'required' : 'required' }), 'hours' : forms.NumberInput(attrs = { 'required' : 'required' }), 'minutes' : forms.NumberInput(attrs = { 'required' : 'required' }), 'seconds' : forms.NumberInput(attrs = { 'required' : 'required' }), 'category' : forms.TextInput(attrs = { 'required' : 'required' }), 'priority' : forms.NumberInput(attrs = { 'required' : 'required' }), } def clean( self ): cleaned_data = super ().clean() hours = cleaned_data.get( 'hours' , 0 ) minutes = cleaned_data.get( 'minutes' , 0 ) seconds = cleaned_data.get( 'seconds' , 0 ) if hours < 0 or minutes < 0 or seconds < 0 : raise forms.ValidationError( "Hours, minutes, and seconds must be non-negative." ) return cleaned_data |
core/urls.py : This Django code sets up URL patterns, linking the ‘admin/’ path to Django’s admin interface and incorporating URLs from the ‘home’ app for the root path. The ‘include’ function enables integrating app-specific URLs into the project’s configuration.
Python3
from django.contrib import admin from django.urls import path, include urlpatterns = [ path( 'admin/' , admin.site.urls), path(' ', include(' home.urls')), ] |
home/urls.py : This Django code defines URL patterns for different views: ‘pomodoro_timer,’ ‘login,’ ‘signup,’ ‘logout,’ ‘add/<int:id>,’ and ‘delete/<str:id>.’ It associates these paths with their respective views, allowing parameterized input for ‘add’ and ‘delete’ views.
Python3
from django.urls import path from .views import pomodoro_timer, login, signup, logout, add, delete urlpatterns = [ path(' ', pomodoro_timer, name=' pomodoro_timer'), path( 'login/' , login, name = 'login' ), path( 'signup/' , signup, name = 'signup' ), path( 'logout/' , logout, name = 'logout' ), path( 'add/<int:id>' , add, name = "add" ), path( 'delete/<str:id>/' , delete, name = "delete" ), ] |
Creating User Interface
login.html : This HTML code defines a Bootstrap-based login page. It includes Bootstrap styles and scripts, displays error messages if any, and provides a form with email and password input fields. The form has a submit button for logging in and a link to the registration page if the user doesn’t have an account.
HTML
<!doctype html> < html lang = "en" > < head > < meta charset = "utf-8" > < meta name = "viewport" content = "width=device-width, initial-scale=1" > < title >Bootstrap demo</ title > < link href = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel = "stylesheet" integrity = "sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin = "anonymous" > </ head > < body > < div class = "bg-body-danger m-3" > {% for message in messages %} < h3 >{{message}}</ h3 > {% endfor %} </ div > < div class = "container mt-5 mx-auto w-50" > < h1 class = "h2 mb-3" >Login Page</ h1 > < form action = "{% url 'login' %}" method = "POST" > {% csrf_token %} < div class = "form-group lh-lg" > < label for = "email" >Email:</ label > < input type = "text" id = "email" name = "email" class = "form-control" required> </ div > < div class = "form-group" > < label for = "password" >Password:</ label > < input type = "password" id = "password" name = "password" class = "form-control" required> </ div > < input type = "submit" value = "Login" class = "btn btn-primary mt-3 text-center" > < p >Don't have an account ? < a href = "{% url 'signup' %}" >Register</ a ></ p > </ form > </ div > < script src = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity = "sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin = "anonymous" ></ script > </ body > </ html > |
pomodromo.html :This HTML page integrates Bootstrap for styling, includes custom CSS and JS files, and features a navigation bar. It displays a Pomodoro timer section with start, pause, reset buttons and dynamically shows user-specific timers, allowing start, pause, reset, and deletion. An editable form for adding new timers is included, and the page adapts based on user authentication status.
HTML
{% load static %} <!DOCTYPE html> < html lang = "en" > < head > < meta charset = "utf-8" > < meta name = "viewport" content = "width=device-width, initial-scale=1" > < title >Bootstrap demo</ title > < link href = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel = "stylesheet" integrity = "sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin = "anonymous" > < link rel = 'stylesheet' href = "{% static 'style.css'%}" > < script src = "{% static 'app.js' %}" ></ script > </ head > < body > < section class = "navbar--section fw-bold" > < nav class = "navbar navbar-expand-lg bg-body-tertiary" > < div class = "container-fluid" > < a class = "navbar-brand" href = "#" >Pomodoro Timer</ a > < button class = "navbar-toggler" type = "button" data-bs-toggle = "collapse" data-bs-target = "#navbarNavAltMarkup" aria-controls = "navbarNavAltMarkup" aria-expanded = "false" aria-label = "Toggle navigation" > < span class = "navbar-toggler-icon" ></ span > </ button > < div class = "collapse navbar-collapse justify-content-end" id = "navbarNavAltMarkup" > < div class = "navbar-nav" > {% if user.is_authenticated %} < a class = "nav-link mx-3" href = "{% url 'add' user.id %}" > < img width = "30" src = "https://img.icons8.com/ios/50/add--v1.png" alt = "add--v1" /> </ a > < a class = "btn btn-primary p-2 fw-bold" href = "{% url 'logout' %}" >Logout</ a > {% else %} < a class = "nav-link" href = "{% url 'login' %}" >Login/SignUp</ a > {% endif %} </ div > </ div > </ div > </ nav > </ section > < section class = "timer--section" > < div class = "container" > < div class = "row" > < div class = "col-lg-12 mt-5 h4 text-center" > < p class = "h2 text-center" >Pomodoro</ p > < div class = "timer " > < span id = "hours" >00</ span >:< span id = "minutes" >25</ span >:< span id = "seconds" >00</ span > </ div > < button class = "btn btn-primary" onclick = "startMainTimer()" >Start</ button > < button class = "btn btn-primary" onclick = "pauseTimer()" >Pause</ button > < button class = "btn btn-primary" onclick = "resetTimer()" >Reset</ button > </ div > </ div > </ div > </ section > < section class = "custom--time" > < div class = "container" > < p class = " h3" >Your Tasks By Categories</ p > {% if user.is_authenticated%} < div class = "row" > {% if editable %} < form id = "pomodoroForm" method = "POST" action = "{% url 'add' user.id %}" > {% csrf_token %} < div class = "h5 col-lg-12 mt-5 editable--form" > {{form}} < div class = "editable--form--btn text-center" > < button class = " btn btn-primary " type = "submit" >Save</ button > </ div > </ div > </ form > {% else %} {% if timers is None %} < div class = "h5 col-lg-12 mt-5 text-center" > < p class = "h4 text-center" >No timer is set</ p > </ div > {% else %} {% for timer in timers %} {% if timer.uuid == user.id %} < div class = "container mt-5 p-5 bg-opacity-10 rounded " > < div class = "row text-center" > < div class = "col " > < p class = "fw-bolder text-center fs-3" >Category : {{ timer.category }}</ p > < p class = "fw-bolder text-center" >Title : {{ timer.title }}</ p > </ div > < div class = "col d-flex align-items-center justify-content-center timer" > < span id = "hours{{ timer.id }}" >{{ timer.hours }}</ span >: < span id = "minutes{{ timer.id }}" >{{ timer.minutes }}</ span >: < span id = "seconds{{ timer.id }}" >{{ timer.seconds }}</ span > < button class = "btn btn-primary m-2" onclick = "startITimer('{{ timer.id }}')" >Start</ button > < button class = "btn btn-primary m-2" onclick = "pauseITimer('{{ timer.id }}')" >Pause</ button > < button class = "btn btn-primary m-2" onclick = "resetITimer('{{ timer.id }}')" >Reset</ button > < a class = "btn btn-danger m-2" href = "/delete/{{ timer.id }}" >Delete</ a > < span id = "initialMinutes{{ timer.id }}" style = "display: none;" >{{ timer.minutes }}</ span > < span id = "initialSeconds{{ timer.id }}" style = "display: none;" >{{ timer.seconds }}</ span > <!-- Add an additional hidden input field to store the timer ID --> < input type = "hidden" class = "timerId" value = "{{ timer.id }}" > </ div > </ div > < div class = "round-time-bar" data-style = "smooth" style = "--duration: 150;" > < div id = "progressBar{{ timer.id }}" ></ div > </ div > </ div > {%endif%} {% endfor %} {% endif %} {% endif %} </ div > {% else %} < div class = "row" > < div class = "col-lg-12 p-5 mt-5 text-center" > < p class = "h3" >Please < a href = "{% url 'login' %}" class = "btn btn-primary" >Login</ a > to see your custom timers</ p > </ div > </ div > {% endif %} </ div > </ section > < script src = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity = "sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin = "anonymous" ></ script > </ body > </ html > |
signup.html : This HTML code creates a Bootstrap-styled signup page with a form. It includes fields for name, email, and password, along with validation attributes. The page displays error messages and provides links for users to navigate between signup and login pages.
HTML
<!doctype html> < html lang = "en" > < head > < meta charset = "utf-8" > < meta name = "viewport" content = "width=device-width, initial-scale=1" > < title >Bootstrap demo</ title > < link href = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel = "stylesheet" integrity = "sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin = "anonymous" > </ head > < body > < div class = "bg-body-danger m-3" > {% for message in messages %} < h3 >{{message}}</ h3 > {% endfor %} </ div > < div class = "container mt-5 mx-auto w-50" > < h1 class = "h2 mb-3" >SignUp Page</ h1 > < form action = "{% url 'signup' %}" method = "POST" > {% csrf_token %} < div class = "form-group lh-lg" > < label for = "username" >Name:</ label > < input type = "text" id = "username" name = "username" class = "form-control" required> </ div > < div class = "form-group lh-lg" > < label for = "email" >Email:</ label > < input type = "text" id = "email" name = "email" class = "form-control" required> </ div > < div class = "form-group" > < label for = "password" >Password:</ label > < input type = "password" id = "password" name = "password" class = "form-control" required> </ div > < input type = "submit" value = "Signup" class = "btn btn-primary mt-3 text-center" > < p >Already have an account ? < a href = "{% url 'login' %}" >Login</ a ></ p > </ form > </ div > < script src = "https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity = "sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin = "anonymous" ></ script > </ body > </ html > |
Static Folder
style.css: This CSS code styles a Pomodoro timer web page, adjusting the positioning, font size, and margins for elements like buttons and timers. It includes a round progress bar for visualizing timer progress and positions an element to the right using the `.add` class.
CSS
.add { margin-left : 70% ; margin-right : 0% ; } .timer { font-size : 2em ; margin-bottom : 20px ; } button { padding : 10px 20px ; font-size : 1em ; cursor : pointer ; margin : 0 5px ; } .editable--form { display : flex; flex- direction : column; padding : 1 rem; width : 50% ; margin : 0 auto ; } .editable--form--btn { padding : 2 rem; } .round-time-bar { width : 100% ; height : 20px ; background-color : #f0f0f0 ; border-radius: 10px ; overflow : hidden ; } .round-time-bar div { height : 100% ; background-color : #4caf50 ; /* Change this color to your preferred color */ border-radius: 10px ; width : 0 ; /* Set initial width to zero */ /* This duration should match the timer duration */ } |
app.js: This JavaScript code defines functions for a main Pomodoro timer and individual timers. It includes functionalities for starting, updating, completing, pausing, resuming, and resetting timers. The main timer runs for 25 minutes, while individual timers are customizable. The code also incorporates audio notifications using the ‘Airtel Mp3 – Airtel Song.mp3’ file.
Javascript
// Main pomodoro timer let mainTimer; let mainSeconds = 0; let mainMinutes = 25; let mainHours = 0; let mainIsTimerRunning = false ; const audio = new Audio( 'static/media/audio/Airtel Mp3 - Airtel Song.mp3' ); function startMainTimer() { if (!mainIsTimerRunning) { mainTimer = setInterval(updateMainTimer, 1000); mainIsTimerRunning = true ; } } function updateMainTimer() { mainSeconds--; if (mainSeconds < 0) { mainSeconds = 59; mainMinutes--; if (mainMinutes < 0) { mainMinutes = 59; mainHours--; if (mainHours < 0) { clearInterval(mainTimer); timerComplete(); return ; } } } updateMainTimerDisplay(); } function updateMainTimerDisplay() { const formattedMainHours = padTime(mainHours); const formattedMainMinutes = padTime(mainMinutes); const formattedMainSeconds = padTime(mainSeconds); document.getElementById( 'hours' ).innerText = formattedMainHours; document.getElementById( 'minutes' ).innerText = formattedMainMinutes; document.getElementById( 'seconds' ).innerText = formattedMainSeconds; } function timerComplete() { audio.play(); } function pauseTimer() { clearInterval(mainTimer); mainIsTimerRunning = false ; } function resetTimer() { clearInterval(mainTimer); mainIsTimerRunning = false ; mainSeconds = 0; mainHours = 0; mainMinutes = 25; updateMainTimerDisplay(); } function padTime(time) { return (time < 10) ? `0${time}` : time; } // For individual timer const timers = {}; function startITimer(timerId) { const initialMinutes = parseInt(document.getElementById(`initialMinutes${timerId}`).innerText) || 0; const initialSeconds = parseInt(document.getElementById(`initialSeconds${timerId}`).innerText) || 0; const totalSeconds = initialMinutes * 60 + initialSeconds; timers[timerId] = { timer: setInterval(() => updateITimer(timerId), 1000), isTimerRunning: true , hours: 0, minutes: initialMinutes, seconds: initialSeconds, totalSeconds: totalSeconds, progressBar: document.getElementById(`progressBar${timerId}`) }; setProgressBarDuration(timerId, totalSeconds); updateITimerDisplay(timerId); } function setProgressBarDuration(timerId, duration) { const progressBar = timers[timerId].progressBar; progressBar.style.setProperty( '--duration' , duration + 's' ); } function updateITimer(timerId) { let timer = timers[timerId]; // Check if the timer is running if (timer.isTimerRunning) { timer.seconds--; if (timer.seconds < 0) { timer.seconds = 59; timer.minutes--; if (timer.minutes < 0) { timer.minutes = 59; timer.hours--; if (timer.hours < 0) { clearInterval(timer.timer); timerComplete(timerId); timer.isTimerRunning = false ; return ; } } } updateITimerDisplay(timerId); updateProgressBar(timerId); } } function updateProgressBar(timerId) { let timer = timers[timerId]; // Check if the timer is still running if (timer.isTimerRunning) { let remainingSeconds = timer.hours * 3600 + timer.minutes * 60 + timer.seconds; let progressPercentage = ((timer.totalSeconds - remainingSeconds) / timer.totalSeconds) * 100; timer.progressBar.style.width = `${progressPercentage}%`; } } function pauseITimer(timerId) { const timer = timers[timerId]; if (timer.isTimerRunning) { clearInterval(timer.timer); timer.isTimerRunning = false ; } } function resumeITimer(timerId) { const timer = timers[timerId]; if (!timer.isTimerRunning) { timer.timer = setInterval(() => updateITimer(timerId), 1000); timer.isTimerRunning = true ; } } function resetITimer(timerId) { const timerIdInput = document.querySelector(`input.timerId[value= '${timerId}' ]`); const initialMinutes = parseInt(document.getElementById(`initialMinutes${timerId}`).innerText) || 0; const initialSeconds = parseInt(document.getElementById(`initialSeconds${timerId}`).innerText) || 0; let timer = timers[timerId]; clearInterval(timer.timer); timer.isTimerRunning = false ; timer.seconds = initialSeconds; timer.hours = 0; timer.minutes = initialMinutes; updateITimerDisplay(timerId); } function timerComplete(timerId) { const audio1 = new Audio( 'static/media/audio/Airtel Mp3 - Airtel Song.mp3' ); console.log( "Played..." ) audio1.play(); } function updateITimerDisplay(timerId) { let timer = timers[timerId]; const formattedHours = padTime(timer.hours); const formattedMinutes = padTime(timer.minutes); const formattedSeconds = padTime(timer.seconds); document.getElementById(`hours${timerId}`).innerText = formattedHours; document.getElementById(`minutes${timerId}`).innerText = formattedMinutes; document.getElementById(`seconds${timerId}`).innerText = formattedSeconds; } function padTime(time) { return (time < 10) ? `0${time}` : time; } |
admin.py:Here we are registering our models.
Python3
from django.contrib import admin from .models import Timers # Register your models here. admin.site.register(Timers) |
Deployement of the Project
Run these commands to apply the migrations:
python3 manage.py makemigrations
python3 manage.py migrate
Run the server with the help of following command:
python3 manage.py runserver
Output
Task Pomodoro Timer using Django
This article explores the development of a Task Pomodoro Timer using Django. Users can set, stop, pause, and delete time intervals for tasks. To engage these features, signing up and logging in are mandatory. The timer offers customization options, allowing users to set seconds, hours, minutes, and categories for efficient time management.