DEV Community

ngemuantony
ngemuantony

Posted on • Edited on

Building a Project Budget Manager with Django - Part 3: Views and Templates

In this third part of our series, we'll create views and templates for our Project Budget Manager. We'll use Tailwind CSS for styling and HTMX for dynamic interactions.

Setting Up Tailwind CSS

  1. First, install Tailwind CSS dependencies:
npm install -D tailwindcss
npx tailwindcss init
Enter fullscreen mode Exit fullscreen mode
  1. Create a tailwind.config.js file:
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./templates/**/*.html",
    "./static/**/*.js",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
Enter fullscreen mode Exit fullscreen mode
  1. Create static/css/input.css:
@tailwind base;
@tailwind components;
@tailwind utilities;

/* Custom styles */
@layer components {
  .btn-primary {
    @apply px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700;
  }
  .btn-secondary {
    @apply px-4 py-2 bg-gray-600 text-white rounded-lg hover:bg-gray-700;
  }
  .form-input {
    @apply mt-1 block w-full rounded-md border-gray-300 shadow-sm;
  }
}
Enter fullscreen mode Exit fullscreen mode

Creating Base Templates

  1. Create templates/layout/base.html:
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>{% block title %}Project Budget Manager{% endblock %}</title>
    <link rel="stylesheet" href="{% static 'css/output.css' %}">
    <script src="{% static 'js/htmx.min.js' %}" defer></script>
    {% block extra_head %}{% endblock %}
</head>
<body class="bg-gray-50">
    {% include "layout/nav.html" %}

    <main class="container mx-auto px-4 py-8">
        {% if messages %}
        <div class="messages mb-8">
            {% for message in messages %}
            <div class="p-4 mb-4 rounded-lg {% if message.tags == 'success' %}bg-green-100 text-green-700{% elif message.tags == 'error' %}bg-red-100 text-red-700{% else %}bg-blue-100 text-blue-700{% endif %}">
                {{ message }}
            </div>
            {% endfor %}
        </div>
        {% endif %}

        {% block content %}{% endblock %}
    </main>

    <footer class="bg-gray-800 text-white py-8 mt-16">
        <div class="container mx-auto px-4">
            <p>&copy; {% now "Y" %} Project Budget Manager. All rights reserved.</p>
        </div>
    </footer>

    {% block extra_js %}{% endblock %}
</body>
</html>
Enter fullscreen mode Exit fullscreen mode

Creating Views

  1. Update app/views.py with our views:
from django.shortcuts import render, redirect, get_object_or_404
from django.contrib.auth.decorators import login_required
from django.contrib import messages
from django.http import HttpResponse
from .models import Project, Expense
from .forms import ProjectForm, ExpenseForm
from decimal import Decimal

@login_required
def dashboard(request):
    user_projects = Project.objects.filter(
        created_by=request.user
    ).order_by('-created_at')
    assigned_projects = Project.objects.filter(
        assigned_to=request.user
    ).order_by('-created_at')

    context = {
        'user_projects': user_projects,
        'assigned_projects': assigned_projects,
    }
    return render(request, 'app/dashboard.html', context)

@login_required
def project_list(request):
    projects = Project.objects.filter(
        created_by=request.user
    ).order_by('-created_at')
    return render(request, 'app/project_list.html', {'projects': projects})

@login_required
def project_detail(request, pk):
    project = get_object_or_404(Project, pk=pk)
    expenses = project.expenses.all().order_by('-date')

    context = {
        'project': project,
        'expenses': expenses,
        'total_expenses': project.get_total_expenses(),
        'budget_remaining': project.get_budget_remaining(),
    }
    return render(request, 'app/project_detail.html', context)

@login_required
def project_create(request):
    if request.method == 'POST':
        form = ProjectForm(request.POST)
        if form.is_valid():
            project = form.save(commit=False)
            project.created_by = request.user
            project.save()
            messages.success(request, 'Project created successfully.')
            return redirect('project_detail', pk=project.pk)
    else:
        form = ProjectForm()

    return render(request, 'app/project_form.html', {'form': form})

@login_required
def expense_create(request, project_pk):
    project = get_object_or_404(Project, pk=project_pk)

    if request.method == 'POST':
        form = ExpenseForm(request.POST, request.FILES)
        if form.is_valid():
            expense = form.save(commit=False)
            expense.project = project
            expense.created_by = request.user
            expense.save()

            if request.htmx:
                return HttpResponse(
                    f'<div id="expense-{expense.id}" class="expense-item">'
                    f'<p>{expense.description} - ${expense.amount}</p></div>'
                )

            messages.success(request, 'Expense added successfully.')
            return redirect('project_detail', pk=project.pk)
    else:
        form = ExpenseForm()

    context = {
        'form': form,
        'project': project,
    }
    return render(request, 'app/expense_form.html', context)
Enter fullscreen mode Exit fullscreen mode
  1. Create app/forms.py:
from django import forms
from .models import Project, Expense

class ProjectForm(forms.ModelForm):
    class Meta:
        model = Project
        fields = ['title', 'description', 'total_budget', 'start_date', 'end_date', 'assigned_to']
        widgets = {
            'start_date': forms.DateInput(attrs={'type': 'date'}),
            'end_date': forms.DateInput(attrs={'type': 'date'}),
        }

class ExpenseForm(forms.ModelForm):
    class Meta:
        model = Expense
        fields = ['description', 'amount', 'category', 'date', 'receipt']
        widgets = {
            'date': forms.DateInput(attrs={'type': 'date'}),
        }
Enter fullscreen mode Exit fullscreen mode

Creating Templates

  1. Create templates/app/dashboard.html:
{% extends "layout/base.html" %}

{% block title %}Dashboard - Project Budget Manager{% endblock %}

{% block content %}
<div class="grid grid-cols-1 md:grid-cols-2 gap-8">
    <div>
        <h2 class="text-2xl font-bold mb-4">Your Projects</h2>
        {% if user_projects %}
            {% for project in user_projects %}
            <div class="bg-white p-6 rounded-lg shadow-md mb-4">
                <h3 class="text-xl font-semibold mb-2">
                    <a href="{% url 'project_detail' pk=project.pk %}" class="text-blue-600 hover:text-blue-800">
                        {{ project.title }}
                    </a>
                </h3>
                <p class="text-gray-600 mb-2">{{ project.description|truncatewords:30 }}</p>
                <div class="flex justify-between items-center">
                    <span class="text-sm text-gray-500">Budget: ${{ project.total_budget }}</span>
                    <span class="px-3 py-1 rounded-full text-sm 
                        {% if project.status == 'approved' %}bg-green-100 text-green-800
                        {% elif project.status == 'pending' %}bg-yellow-100 text-yellow-800
                        {% elif project.status == 'rejected' %}bg-red-100 text-red-800
                        {% else %}bg-gray-100 text-gray-800{% endif %}">
                        {{ project.get_status_display }}
                    </span>
                </div>
            </div>
            {% endfor %}
        {% else %}
            <p class="text-gray-600">No projects created yet.</p>
        {% endif %}

        <a href="{% url 'project_create' %}" class="btn-primary inline-block mt-4">
            Create New Project
        </a>
    </div>

    <div>
        <h2 class="text-2xl font-bold mb-4">Assigned Projects</h2>
        {% if assigned_projects %}
            {% for project in assigned_projects %}
            <div class="bg-white p-6 rounded-lg shadow-md mb-4">
                <h3 class="text-xl font-semibold mb-2">
                    <a href="{% url 'project_detail' pk=project.pk %}" class="text-blue-600 hover:text-blue-800">
                        {{ project.title }}
                    </a>
                </h3>
                <p class="text-gray-600 mb-2">{{ project.description|truncatewords:30 }}</p>
                <div class="flex justify-between items-center">
                    <span class="text-sm text-gray-500">Created by: {{ project.created_by.get_full_name|default:project.created_by.username }}</span>
                    <span class="px-3 py-1 rounded-full text-sm 
                        {% if project.status == 'approved' %}bg-green-100 text-green-800
                        {% elif project.status == 'pending' %}bg-yellow-100 text-yellow-800
                        {% elif project.status == 'rejected' %}bg-red-100 text-red-800
                        {% else %}bg-gray-100 text-gray-800{% endif %}">
                        {{ project.get_status_display }}
                    </span>
                </div>
            </div>
            {% endfor %}
        {% else %}
            <p class="text-gray-600">No projects assigned to you.</p>
        {% endif %}
    </div>
</div>
{% endblock %}
Enter fullscreen mode Exit fullscreen mode

Setting Up URLs

Update app/urls.py:

from django.urls import path
from . import views

app_name = 'app'

urlpatterns = [
    path('', views.dashboard, name='dashboard'),
    path('projects/', views.project_list, name='project_list'),
    path('projects/create/', views.project_create, name='project_create'),
    path('projects/<int:pk>/', views.project_detail, name='project_detail'),
    path('projects/<int:project_pk>/expenses/create/', 
         views.expense_create, name='expense_create'),
]
Enter fullscreen mode Exit fullscreen mode

Next Steps

In Part 4 of this series, we'll:

  • Implement project approval workflow
  • Add email notifications
  • Create project reports and analytics
  • Set up production deployment

Resources


This article is part of the "Building a Project Budget Manager with Django" series. Check out Part 1 and Part 2 if you haven't already!

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (2)

Collapse
 
tpython profile image
kenny li

I first encountered Django around 2009. At that time, I found Django very useful, especially for its admin functionality. However, due to various reasons, I switched back to PHP. As PHP frameworks started to emerge, particularly in recent years with the rise of the Laravel framework, I now find that PHP is still more suitable for building websites when I look back at Django.

Collapse
 
ngemuantony profile image
ngemuantony

As long as it serves the purpose, there's no problem sticking with it. At the end of the day, developers are solution-oriented and focused on addressing problems.

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay