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
  1. Create a tailwind.config.js file:
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./templates/**/*.html",
    "./static/**/*.js",
  ],
  theme: {
    extend: {},
  },
  plugins: [],
}
  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;
  }
}

Creating Base Templates

  1. Create templates/layout/base.html:
{% load static %}
</span>
 lang="en">

     charset="UTF-8">
     name="viewport" content="width=device-width, initial-scale=1.0">
    {% block title %}Project Budget Manager{% endblock %}
     rel="stylesheet" href="{% static 'css/output.css' %}">
    <span class="na">src="{% static 'js/htmx.min.js' %}" defer>
    {% block extra_head %}{% endblock %}

 class="bg-gray-50">
    {% include "layout/nav.html" %}

     class="container mx-auto px-4 py-8">
        {% if messages %}
         class="messages mb-8">
            {% for message in messages %}
             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 }}
            
            {% endfor %}
        
        {% endif %}

        {% block content %}{% endblock %}
    

     class="bg-gray-800 text-white py-8 mt-16">
         class="container mx-auto px-4">
            © {% now "Y" %} Project Budget Manager. All rights reserved.
        
    

    {% block extra_js %}{% endblock %}





    Enter fullscreen mode
    


    Exit fullscreen mode
    





  
  
  Creating Views

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'"expense-{expense.id}" class="expense-item">'
                    f'{expense.description} - ${expense.amount}'
                )

            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
    





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

Create templates/app/dashboard.html:


{% extends "layout/base.html" %}

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

{% block content %}
 class="grid grid-cols-1 md:grid-cols-2 gap-8">
    
         class="text-2xl font-bold mb-4">Your Projects
        {% if user_projects %}
            {% for project in user_projects %}
             class="bg-white p-6 rounded-lg shadow-md mb-4">
                 class="text-xl font-semibold mb-2">
                     href="{% url 'project_detail' pk=project.pk %}" class="text-blue-600 hover:text-blue-800">
                        {{ project.title }}
                    
                
                 class="text-gray-600 mb-2">{{ project.description|truncatewords:30 }}
                 class="flex justify-between items-center">
                     class="text-sm text-gray-500">Budget: ${{ project.total_budget }}
                     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 }}
                    
                
            
            {% endfor %}
        {% else %}
             class="text-gray-600">No projects created yet.
        {% endif %}

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

    
         class="text-2xl font-bold mb-4">Assigned Projects
        {% if assigned_projects %}
            {% for project in assigned_projects %}
             class="bg-white p-6 rounded-lg shadow-md mb-4">
                 class="text-xl font-semibold mb-2">
                     href="{% url 'project_detail' pk=project.pk %}" class="text-blue-600 hover:text-blue-800">
                        {{ project.title }}
                    
                
                 class="text-gray-600 mb-2">{{ project.description|truncatewords:30 }}
                 class="flex justify-between items-center">
                     class="text-sm text-gray-500">Created by: {{ project.created_by.get_full_name|default:project.created_by.username }}
                     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 }}
                    
                
            
            {% endfor %}
        {% else %}
             class="text-gray-600">No projects assigned to you.
        {% endif %}
    

{% 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//', views.project_detail, name='project_detail'),
    path('projects//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

Django Templates Documentation
Tailwind CSS Documentation
HTMX Documentation
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!