In this article, we will guide you through creating an Issue Tracker using Django. A Django issue tracker is a web-based application designed to help software development teams manage and track bugs, tasks, and feature requests efficiently. Leveraging Django’s powerful framework, this project provides a structured approach to issue management through robust models, views, and templates. Users can create and manage projects, log issues, update their statuses, and add comments for better collaboration.
Issue Tracker using DjangoBelow, is the step-by-step Implementation of a language learning app using Django:
Starting the Project FolderTo start the project use this command
django-admin startproject issue cd issue 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", ] File Structure
.png) Setting Necessary Fileshome/models.py: The models.py file in a Django issue tracker project defines the data structure of the application. It uses Django’s ORM (Object-Relational Mapping) system to create database tables and establish relationships between different data entities.
Python
from django.db import models
from django.contrib.auth.models import User
class Project(models.Model):
name = models.CharField(max_length=100)
description = models.TextField()
def __str__(self):
return self.name
class Issue(models.Model):
STATUS_CHOICES = [
('open', 'Open'),
('in_progress', 'In Progress'),
('closed', 'Closed'),
]
title = models.CharField(max_length=100)
description = models.TextField()
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='open')
project = models.ForeignKey(Project, related_name='issues',
on_delete=models.CASCADE)
created_by = models.ForeignKey(User, related_name='issues_created',
on_delete=models.CASCADE)
assigned_to = models.ForeignKey(User, related_name='issues_assigned',
on_delete=models.SET_NULL, null=True, blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.title
class Comment(models.Model):
issue = models.ForeignKey(Issue, related_name='comments',
on_delete=models.CASCADE)
author = models.ForeignKey(User, related_name='comments',
on_delete=models.CASCADE)
text = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return f'Comment by {self.author} on {self.issue}'
home/forms.py: The forms.py file in a Django issue tracker project defines forms that handle user input for creating and updating projects, issues, and comments. Django’s form classes provide a way to validate and process user data while rendering the form fields as HTML elements.
Python
from django import forms
from .models import Issue,Project
class IssueForm(forms.ModelForm):
class Meta:
model = Issue
fields = ['title', 'description', 'status', 'assigned_to']
widgets = {
'title': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control'}),
'status': forms.Select(attrs={'class': 'form-control'}),
'assigned_to': forms.Select(attrs={'class': 'form-control'}),
}
from .models import Comment
class CommentForm(forms.ModelForm):
class Meta:
model = Comment
fields = ['text']
widgets = {
'text': forms.Textarea(attrs={'class': 'form-control', 'rows': 3}),
}
class ProjectForm(forms.ModelForm):
class Meta:
model = Project
fields = ['name', 'description']
widgets = {
'name': forms.TextInput(attrs={'class': 'form-control'}),
'description': forms.Textarea(attrs={'class': 'form-control'}),
}
home/views.py: The views.py file in a Django issue tracker project contains the logic that connects the models to the templates and handles HTTP requests and responses.
Python
from django.shortcuts import render, get_object_or_404,redirect
from .models import Project, Issue,Comment
from .forms import IssueForm
from .forms import ProjectForm, CommentForm
def create_project(request):
if request.method == 'POST':
form = ProjectForm(request.POST)
if form.is_valid():
form.save()
return redirect('project_list')
else:
form = ProjectForm()
return render(request, 'project_form.html', {'form': form})
def project_list(request):
projects = Project.objects.all()
return render(request, 'project_list.html', {'projects': projects})
def project_detail(request, pk):
project = get_object_or_404(Project, pk=pk)
issues = project.issues.all()
return render(request, 'project_detail.html', {'project': project, 'issues': issues})
def issue_detail(request, pk):
issue = get_object_or_404(Issue, pk=pk)
return render(request, 'issue_detail.html', {'issue': issue})
def create_issue(request, pk):
project = get_object_or_404(Project, pk=pk)
if request.method == 'POST':
form = IssueForm(request.POST)
if form.is_valid():
issue = form.save(commit=False)
issue.project = project
issue.created_by = request.user
issue.save()
return redirect('project_detail', pk=project.pk)
else:
form = IssueForm()
return render(request, 'issue_form.html', {'form': form, 'project': project})
from .forms import CommentForm
def issue_detail(request, pk):
issue = get_object_or_404(Issue, pk=pk)
comments = issue.comments.all()
if request.method == 'POST':
comment_form = CommentForm(request.POST)
if comment_form.is_valid():
comment = comment_form.save(commit=False)
comment.issue = issue
comment.author = request.user
comment.save()
return redirect('issue_detail', pk=issue.pk)
else:
comment_form = CommentForm()
return render(request, 'issue_detail.html', {
'issue': issue,
'comments': comments,
'comment_form': comment_form,
}
)
templates/create_community_postThe templates directory contains HTML files that define the user interface. These templates use Django’s template language and Bootstrap to create a responsive and attractive UI for listing projects, displaying issue details, and handling form submissions.
templates/base.html:
HTML
<!DOCTYPE html>
<html>
<head>
<title>{% block title %}Issue Tracker{% endblock %}</title>
<link href="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css" rel="stylesheet">
<style>
.navbar-custom {
background-color: #343a40;
}
.navbar-custom .navbar-brand, .navbar-custom .nav-link {
color: #ffffff;
}
.navbar-custom .nav-link:hover {
color: #f8f9fa;
}
</style>
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-custom">
<a class="navbar-brand" href="{% url 'project_list' %}">Issue Tracker</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav mr-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'project_list' %}">Projects</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'create_project' %}">Create Project</a>
</li>
<!-- Add more nav items here -->
</ul>
<form class="form-inline my-2 my-lg-0">
<input class="form-control mr-sm-2" type="search" placeholder="Search" aria-label="Search">
<button class="btn btn-outline-light my-2 my-sm-0" type="submit">Search</button>
</form>
</div>
</nav>
<div class="container mt-4">
{% block content %}{% endblock %}
</div>
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/@popperjs/[email protected]/dist/umd/popper.min.js"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
</body>
</html>
templates/issue_detail.hmtl:
Python
{% extends 'base.html' %}
{% block title %}{{ issue.title }}{% endblock %}
{% block content %}
<div class="mt-5">
<h1>{{ issue.title }}</h1>
<p>{{ issue.description }}</p>
<p>Status: <span class="badge badge-info">{{ issue.get_status_display }}</span></p>
<p>Assigned to: {{ issue.assigned_to }}</p>
<h2 class="mt-4">Comments</h2>
<ul class="list-group mt-3">
{% for comment in comments %}
<li class="list-group-item">
<strong>{{ comment.author }}</strong> ({{ comment.created_at }}):
<p>{{ comment.text }}</p>
</li>
{% empty %}
<li class="list-group-item">No comments yet.</li>
{% endfor %}
</ul>
<h2 class="mt-4">Add a comment</h2>
<form method="post" class="mt-3">
{% csrf_token %}
<div class="form-group">
{{ comment_form.text.label_tag }}
{{ comment_form.text }}
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
{% endblock %}
templates/issue_form.html:
HTML
{% extends 'base.html' %}
{% block title %}Create Issue{% endblock %}
{% block content %}
<div class="mt-5">
<h1>Create Issue for {{ project.name }}</h1>
<form method="post">
{% csrf_token %}
<div class="form-group">
{{ form.title.label_tag }}
{{ form.title }}
</div>
<div class="form-group">
{{ form.description.label_tag }}
{{ form.description }}
</div>
<div class="form-group">
{{ form.status.label_tag }}
{{ form.status }}
</div>
<div class="form-group">
{{ form.assigned_to.label_tag }}
{{ form.assigned_to }}
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
{% endblock %}
templates/project_detail.html:
HTML
<!DOCTYPE html>
<html>
<head>
<title>{{ project.name }}</title>
</head>
<body>
<h1>{{ project.name }}</h1>
<p>{{ project.description }}</p>
<h2>Issues</h2>
<ul>
{% for issue in issues %}
<li><a href="{% url 'issue_detail' issue.pk %}">{{ issue.title }}</a></li>
{% endfor %}
</ul>
<a href="{% url 'create_issue' project.pk %}">Create new issue</a>
</body>
</html>
templates/project_form.html:
HTML
{% extends 'base.html' %}
{% block title %}Create Project{% endblock %}
{% block content %}
<div class="mt-5">
<h1>Create Project</h1>
<form method="post">
{% csrf_token %}
<div class="form-group">
{{ form.name.label_tag }}
{{ form.name }}
</div>
<div class="form-group">
{{ form.description.label_tag }}
{{ form.description }}
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
{% endblock %}
templates/project_list.html:
HTML
{% extends 'base.html' %}
{% block title %}Projects{% endblock %}
{% block content %}
<h1 class="mt-5">Projects</h1>
<a href="{% url 'create_project' %}" class="btn btn-primary mb-3">Create New Project</a>
<ul class="list-group mt-3">
{% for project in projects %}
<li class="list-group-item">
<a href="{% url 'project_detail' project.pk %}">{{ project.name }}</a>
</li>
{% endfor %}
</ul>
{% endblock %}
home/urls.py: This urlpatterns list defines paths for the Django project: admin/ for the admin interface and ” for URLs defined in the “home” app
Python
from django.urls import path
from . import views
urlpatterns = [
path('', views.project_list, name='project_list'),
path('project/<int:pk>/', views.project_detail, name='project_detail'),
path('issue/<int:pk>/', views.issue_detail, name='issue_detail'),
path('project/<int:pk>/new_issue/', views.create_issue, name='create_issue'),
path('project/new/', views.create_project, name='create_project'),
]
issue/urls.py: These urlpatterns in Django project correspond to views.
Python
from django.contrib import admin
from django.urls import path,include
urlpatterns = [
path('admin/', admin.site.urls),
path('',include("home.urls"))
]
admin.py:
Python
from django.contrib import admin
from .models import Project, Issue, Comment
admin.site.register(Project)
admin.site.register(Issue)
admin.site.register(Comment)
Output:
-(1).png) .png) .png)
|