Building a Project Budget Manager with Django – Part 6: Advanced Features

Building a Project Budget Manager with Django (9 Part Series)

1 Deploying a Django Application ON “pythonanywhere”.
2 Project Budget Manager Tutorial Series
5 more parts…
3 Part 7: Deploying to PythonAnywhere
4 Building a Project Budget Manager with Django – Part 6: Advanced Features
5 Building a Project Budget Manager with Django – Part 5: Templates and Documentation
6 Building a Project Budget Manager with Django – Part 4: Deployment and Production Setup On Ubuntu Server
7 Building a Project Budget Manager with Django – Part 3: Views and Templates
8 Building a Project Budget Manager with Django – Part 2: Authentication and Models
9 Building a Project Budget Manager with Django – Part 1: Project Setup and Structure

Custom User Management

  1. Create a custom user app (users/models.py):
from django.contrib.auth.models import AbstractUser
from django.db import models

class CustomUser(AbstractUser):
    """ Custom user model with additional fields """
    department = models.CharField(max_length=100, blank=True)
    position = models.CharField(max_length=100, blank=True)

    def get_full_name(self):
        return f"{self.first_name} {self.last_name}"

Enter fullscreen mode Exit fullscreen mode

  1. Update settings to use custom user (config/settings/base.py):
AUTH_USER_MODEL = 'users.CustomUser'

Enter fullscreen mode Exit fullscreen mode

  1. Create user forms (users/forms.py):
from django.contrib.auth.forms import UserCreationForm, UserChangeForm
from .models import CustomUser

class CustomUserCreationForm(UserCreationForm):
    class Meta:
        model = CustomUser
        fields = ('username', 'email', 'department', 'position')

class CustomUserChangeForm(UserChangeForm):
    class Meta:
        model = CustomUser
        fields = ('username', 'email', 'department', 'position')

Enter fullscreen mode Exit fullscreen mode

Admin Dashboard Customization

  1. Create a custom admin dashboard (app/admin.py):
from django.contrib import admin
from django.db.models import Sum
from django.utils.html import format_html
from .models import Project, Expense, ProjectComment

@admin.register(Project)
class ProjectAdmin(admin.ModelAdmin):
    list_display = ('title', 'created_by', 'status', 'total_budget', 'budget_usage', 'created_at')
    list_filter = ('status', 'created_at')
    search_fields = ('title', 'description', 'created_by__username')
    readonly_fields = ('created_at', 'updated_at')

    def budget_usage(self, obj):
        total_expenses = obj.get_total_expenses()
        percentage = (total_expenses / obj.total_budget * 100) if obj.total_budget else 0
        color = 'green' if percentage <= 75 else 'orange' if percentage <= 100 else 'red'
        return format_html(
            '<div style="color: {};">{:.1f}% (${:,.2f} / ${:,.2f})</div>',
            color, percentage, total_expenses, obj.total_budget
        )
    budget_usage.short_description = 'Budget Usage'

@admin.register(Expense)
class ExpenseAdmin(admin.ModelAdmin):
    list_display = ('description', 'project', 'amount', 'category', 'date')
    list_filter = ('category', 'date', 'project')
    search_fields = ('description', 'project__title')
    date_hierarchy = 'date'

    def get_queryset(self, request):
        qs = super().get_queryset(request)
        if not request.user.is_superuser:
            qs = qs.filter(project__created_by=request.user)
        return qs

Enter fullscreen mode Exit fullscreen mode

Utility Functions

  1. Create email utilities (app/utils/emails.py):
from django.core.mail import send_mail
from django.template.loader import render_to_string
from django.conf import settings

def send_project_status_notification(project, recipient_list):
    """ Send email notification when project status changes Args: project: Project instance that was updated recipient_list: List of email addresses to notify """
    context = {
        'project': project,
        'status': project.get_status_display(),
    }

    # Render email templates     html_message = render_to_string(
        'account/email/project_status_update.html',
        context
    )
    plain_message = render_to_string(
        'account/email/project_status_update.txt',
        context
    )

    send_mail(
        subject=f'Project Status Update: {project.title}',
        message=plain_message,
        html_message=html_message,
        from_email=settings.DEFAULT_FROM_EMAIL,
        recipient_list=recipient_list
    )

Enter fullscreen mode Exit fullscreen mode

JavaScript Integration

  1. Create project form handling (static/js/project-form.js):
document.addEventListener('DOMContentLoaded', function() {
    // Initialize date pickers
    const datePickers = document.querySelectorAll('input[type="date"]');
    datePickers.forEach(picker => {
        // Add any date picker initialization here
    });

    // Budget calculation
    const budgetInput = document.getElementById('id_total_budget');
    const expenseInputs = document.querySelectorAll('.expense-amount');

    function updateTotalExpenses() {
        const total = Array.from(expenseInputs)
            .reduce((sum, input) => sum + (parseFloat(input.value) || 0), 0);
        document.getElementById('total-expenses').textContent = total.toFixed(2);

        const budget = parseFloat(budgetInput.value) || 0;
        const remaining = budget - total;
        const remainingElement = document.getElementById('budget-remaining');

        remainingElement.textContent = remaining.toFixed(2);
        remainingElement.classList.toggle('text-red-600', remaining < 0);
        remainingElement.classList.toggle('text-green-600', remaining >= 0);
    }

    expenseInputs.forEach(input => {
        input.addEventListener('change', updateTotalExpenses);
    });
    budgetInput.addEventListener('change', updateTotalExpenses);
});

Enter fullscreen mode Exit fullscreen mode

  1. HTMX Integration (templates/projects/detail.html):
{% extends "layout/dashboard/layout.html" %}
{% load static %}

{% block extra_head %}
<script src="{% static 'js/htmx.min.js' %}" defer></script>
{% endblock %}

{% block content %}
<div class="container mx-auto px-4">
    <!-- Quick Add Expense Form -->
    <form hx-post="{% url 'expense_create' project.id %}"
          hx-target="#expense-list"
          hx-swap="afterbegin"
          class="mb-8">
        {% csrf_token %}
        <div class="flex gap-4">
            <input type="text" 
                   name="description" 
                   placeholder="Expense description"
                   class="form-input flex-1">
            <input type="number" 
                   name="amount" 
                   placeholder="Amount"
                   class="form-input w-32">
            <button type="submit" 
                    class="btn-primary">
                Add Expense
            </button>
        </div>
    </form>

    <!-- Expense List -->
    <div id="expense-list">
        {% for expense in expenses %}
            {% include "expenses/_expense_item.html" %}
        {% endfor %}
    </div>
</div>
{% endblock %}

Enter fullscreen mode Exit fullscreen mode

Tailwind Configuration

  1. Update tailwind.config.js:
/** @type {import('tailwindcss').Config} */
module.exports = {
  content: [
    "./templates/**/*.html",
    "./static/**/*.js",
  ],
  theme: {
    extend: {
      colors: {
        primary: {
          50: '#f0f9ff',
          100: '#e0f2fe',
          // ... other shades
          900: '#0c4a6e',
        },
      },
      spacing: {
        '128': '32rem',
      },
    },
  },
  plugins: [
    require('@tailwindcss/forms'),
  ],
}

Enter fullscreen mode Exit fullscreen mode

  1. Create build script in package.json:
{ "scripts": { "build": "tailwindcss -i ./static/css/input.css -o ./static/css/output.css --watch" }, "dependencies": { "tailwindcss": "^3.0.0", "@tailwindcss/forms": "^0.5.0" } } 

Enter fullscreen mode Exit fullscreen mode

Testing

  1. Create tests for models (app/tests/test_models.py):
from django.test import TestCase
from django.contrib.auth import get_user_model
from app.models import Project, Expense
from decimal import Decimal

User = get_user_model()

class ProjectTests(TestCase):
    def setUp(self):
        self.user = User.objects.create_user(
            username='testuser',
            password='testpass123'
        )
        self.project = Project.objects.create(
            title='Test Project',
            description='Test Description',
            created_by=self.user,
            total_budget=Decimal('1000.00')
        )

    def test_project_creation(self):
        self.assertEqual(self.project.title, 'Test Project')
        self.assertEqual(self.project.status, 'draft')
        self.assertEqual(self.project.get_total_expenses(), Decimal('0'))

    def test_budget_calculations(self):
        Expense.objects.create(
            project=self.project,
            description='Test Expense',
            amount=Decimal('500.00'),
            category='materials',
            created_by=self.user
        )

        self.assertEqual(self.project.get_total_expenses(), Decimal('500.00'))
        self.assertEqual(self.project.get_budget_remaining(), Decimal('500.00'))
        self.assertFalse(self.project.is_over_budget())

Enter fullscreen mode Exit fullscreen mode

Security Enhancements

  1. Add security middleware (config/settings/production.py):
MIDDLEWARE += [
    'django.middleware.security.SecurityMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
]

# Security settings SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SECURE_SSL_REDIRECT = True
SECURE_HSTS_SECONDS = 31536000  # 1 year SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True

Enter fullscreen mode Exit fullscreen mode

Resources


This article is part of the “Building a Project Budget Manager with Django” series. Check out Part 1, Part 2,Part 3, Part 4, and Part 5 if you haven’t already!

Building a Project Budget Manager with Django (9 Part Series)

1 Deploying a Django Application ON “pythonanywhere”.
2 Project Budget Manager Tutorial Series
5 more parts…
3 Part 7: Deploying to PythonAnywhere
4 Building a Project Budget Manager with Django – Part 6: Advanced Features
5 Building a Project Budget Manager with Django – Part 5: Templates and Documentation
6 Building a Project Budget Manager with Django – Part 4: Deployment and Production Setup On Ubuntu Server
7 Building a Project Budget Manager with Django – Part 3: Views and Templates
8 Building a Project Budget Manager with Django – Part 2: Authentication and Models
9 Building a Project Budget Manager with Django – Part 1: Project Setup and Structure

原文链接:Building a Project Budget Manager with Django – Part 6: Advanced Features

© 版权声明
THE END
喜欢就支持一下吧
点赞6 分享
Wish my smile clear off the sky, of all days.
微笑可以晴朗所有的天
评论 抢沙发

请登录后发表评论

    暂无评论内容