Templates and Presentation Layer

Including Static Files

Static files (CSS, JavaScript, images, fonts) are essential components of modern web applications. Django provides a robust system for managing, serving, and optimizing static files across development and production environments.

Including Static Files

Static files (CSS, JavaScript, images, fonts) are essential components of modern web applications. Django provides a robust system for managing, serving, and optimizing static files across development and production environments.

Static Files Overview

What Are Static Files?

Static files are assets that don't change based on user requests or application state:

  • CSS stylesheets - Visual styling and layout
  • JavaScript files - Client-side functionality and interactivity
  • Images - Icons, logos, backgrounds, and graphics
  • Fonts - Custom typography files
  • Documents - PDFs, downloads, and other static resources

Static vs Media Files

Static Files:

  • Part of your application code
  • Versioned with your codebase
  • Same for all users
  • Examples: CSS, JS, app icons

Media Files:

  • User-uploaded content
  • Dynamic and user-specific
  • Not versioned with code
  • Examples: profile photos, document uploads

Static Files Configuration

Basic Settings

# settings.py
import os
from pathlib import Path

BASE_DIR = Path(__file__).resolve().parent.parent

# Static files (CSS, JavaScript, Images)
STATIC_URL = '/static/'  # URL prefix for static files

# Additional locations of static files
STATICFILES_DIRS = [
    BASE_DIR / 'static',  # Project-level static files
    BASE_DIR / 'assets',  # Alternative static directory
]

# Directory where collected static files will be stored
STATIC_ROOT = BASE_DIR / 'staticfiles'  # For production

# Static files finders
STATICFILES_FINDERS = [
    'django.contrib.staticfiles.finders.FileSystemFinder',
    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
]

Directory Structure

myproject/
├── static/                    # Project-level static files
│   ├── css/
│   │   ├── main.css
│   │   ├── bootstrap.min.css
│   │   └── components/
│   │       ├── navbar.css
│   │       └── footer.css
│   ├── js/
│   │   ├── main.js
│   │   ├── jquery.min.js
│   │   └── components/
│   │       ├── carousel.js
│   │       └── modal.js
│   ├── images/
│   │   ├── logo.png
│   │   ├── favicon.ico
│   │   └── backgrounds/
│   │       ├── hero.jpg
│   │       └── pattern.png
│   └── fonts/
│       ├── custom-font.woff2
│       └── icons.ttf
├── blog/
│   └── static/
│       └── blog/              # App-specific static files
│           ├── css/
│           │   └── blog.css
│           ├── js/
│           │   └── blog.js
│           └── images/
│               └── blog-icon.png
└── staticfiles/               # Collected static files (production)

Using Static Files in Templates

Loading the Static Template Tag

<!-- Load static tag at the top of templates -->
{% load static %}

<!DOCTYPE html>
<html>
<head>
    <title>My Django Site</title>
    
    <!-- CSS files -->
    <link rel="stylesheet" href="{% static 'css/bootstrap.min.css' %}">
    <link rel="stylesheet" href="{% static 'css/main.css' %}">
    <link rel="stylesheet" href="{% static 'blog/css/blog.css' %}">
    
    <!-- Favicon -->
    <link rel="icon" href="{% static 'images/favicon.ico' %}">
</head>
<body>
    <!-- Content here -->
    
    <!-- JavaScript files -->
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <script src="{% static 'js/bootstrap.min.js' %}"></script>
    <script src="{% static 'js/main.js' %}"></script>
</body>
</html>

Static File Examples

CSS Stylesheets:

{% load static %}

<!-- Main stylesheet -->
<link rel="stylesheet" href="{% static 'css/main.css' %}">

<!-- App-specific styles -->
<link rel="stylesheet" href="{% static 'blog/css/blog.css' %}">

<!-- Conditional CSS -->
{% if user.is_authenticated %}
    <link rel="stylesheet" href="{% static 'css/authenticated.css' %}">
{% endif %}

<!-- Media queries -->
<link rel="stylesheet" href="{% static 'css/mobile.css' %}" media="(max-width: 768px)">
<link rel="stylesheet" href="{% static 'css/print.css' %}" media="print">

JavaScript Files:

{% load static %}

<!-- Core JavaScript -->
<script src="{% static 'js/jquery.min.js' %}"></script>
<script src="{% static 'js/main.js' %}"></script>

<!-- Conditional JavaScript -->
{% if debug %}
    <script src="{% static 'js/debug.js' %}"></script>
{% endif %}

<!-- Async loading -->
<script src="{% static 'js/analytics.js' %}" async></script>

<!-- Deferred loading -->
<script src="{% static 'js/non-critical.js' %}" defer></script>

Images:

{% load static %}

<!-- Logo -->
<img src="{% static 'images/logo.png' %}" alt="Company Logo" class="logo">

<!-- Background images in CSS -->
<div class="hero" style="background-image: url('{% static 'images/hero-bg.jpg' %}');">
    <h1>Welcome</h1>
</div>

<!-- Responsive images -->
<img src="{% static 'images/hero-mobile.jpg' %}" 
     srcset="{% static 'images/hero-mobile.jpg' %} 480w,
             {% static 'images/hero-tablet.jpg' %} 768w,
             {% static 'images/hero-desktop.jpg' %} 1200w"
     sizes="(max-width: 480px) 480px,
            (max-width: 768px) 768px,
            1200px"
     alt="Hero Image">

<!-- SVG icons -->
<img src="{% static 'images/icons/search.svg' %}" alt="Search" class="icon">

Advanced Static File Usage

Dynamic Static File Paths:

{% load static %}

<!-- Using variables -->
{% with icon_name='user-profile' %}
    <img src="{% static 'images/icons/'|add:icon_name|add:'.svg' %}" alt="User Profile">
{% endwith %}

<!-- Loop through static files -->
{% for social in social_networks %}
    <a href="{{ social.url }}">
        <img src="{% static 'images/social/'|add:social.name|add:'.png' %}" 
             alt="{{ social.name }}">
    </a>
{% endfor %}

Inline CSS with Static URLs:

{% load static %}

<style>
    .custom-bg {
        background-image: url('{% static "images/pattern.png" %}');
        background-repeat: repeat;
    }
    
    .logo::before {
        content: url('{% static "images/logo-small.svg" %}');
    }
</style>

CSS Management

Organizing CSS Files

Main CSS Structure:

/* static/css/main.css */

/* CSS Reset/Normalize */
@import url('normalize.css');

/* Base styles */
@import url('base.css');

/* Layout components */
@import url('components/header.css');
@import url('components/navigation.css');
@import url('components/footer.css');

/* Utility classes */
@import url('utilities.css');

/* Responsive styles */
@import url('responsive.css');

Component-Based CSS:

/* static/css/components/card.css */
.card {
    border: 1px solid #ddd;
    border-radius: 8px;
    padding: 1rem;
    margin-bottom: 1rem;
    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}

.card-header {
    border-bottom: 1px solid #eee;
    padding-bottom: 0.5rem;
    margin-bottom: 1rem;
}

.card-title {
    margin: 0;
    font-size: 1.25rem;
    font-weight: 600;
}

.card-body {
    line-height: 1.6;
}

.card-footer {
    border-top: 1px solid #eee;
    padding-top: 0.5rem;
    margin-top: 1rem;
    text-align: right;
}

CSS Variables and Custom Properties:

/* static/css/variables.css */
:root {
    /* Colors */
    --primary-color: #007bff;
    --secondary-color: #6c757d;
    --success-color: #28a745;
    --danger-color: #dc3545;
    --warning-color: #ffc107;
    --info-color: #17a2b8;
    
    /* Typography */
    --font-family-base: 'Helvetica Neue', Arial, sans-serif;
    --font-size-base: 1rem;
    --line-height-base: 1.5;
    
    /* Spacing */
    --spacing-xs: 0.25rem;
    --spacing-sm: 0.5rem;
    --spacing-md: 1rem;
    --spacing-lg: 1.5rem;
    --spacing-xl: 3rem;
    
    /* Breakpoints */
    --breakpoint-sm: 576px;
    --breakpoint-md: 768px;
    --breakpoint-lg: 992px;
    --breakpoint-xl: 1200px;
}

/* Usage */
.btn-primary {
    background-color: var(--primary-color);
    padding: var(--spacing-sm) var(--spacing-md);
    font-family: var(--font-family-base);
}

Responsive CSS

/* static/css/responsive.css */

/* Mobile First Approach */
.container {
    width: 100%;
    padding: 0 var(--spacing-md);
}

.grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: var(--spacing-md);
}

/* Tablet */
@media (min-width: 768px) {
    .container {
        max-width: 750px;
        margin: 0 auto;
    }
    
    .grid {
        grid-template-columns: repeat(2, 1fr);
    }
    
    .grid-3 {
        grid-template-columns: repeat(3, 1fr);
    }
}

/* Desktop */
@media (min-width: 992px) {
    .container {
        max-width: 970px;
    }
    
    .grid {
        grid-template-columns: repeat(3, 1fr);
    }
    
    .grid-4 {
        grid-template-columns: repeat(4, 1fr);
    }
}

/* Large Desktop */
@media (min-width: 1200px) {
    .container {
        max-width: 1170px;
    }
}

JavaScript Management

JavaScript File Organization

// static/js/main.js

// Namespace for application
window.MyApp = window.MyApp || {};

// Utility functions
MyApp.utils = {
    // CSRF token helper
    getCsrfToken: function() {
        return document.querySelector('[name=csrfmiddlewaretoken]').value;
    },
    
    // AJAX helper
    ajax: function(url, options) {
        const defaults = {
            method: 'GET',
            headers: {
                'Content-Type': 'application/json',
                'X-CSRFToken': this.getCsrfToken()
            }
        };
        
        return fetch(url, Object.assign(defaults, options));
    },
    
    // DOM ready helper
    ready: function(fn) {
        if (document.readyState !== 'loading') {
            fn();
        } else {
            document.addEventListener('DOMContentLoaded', fn);
        }
    }
};

// Initialize application
MyApp.utils.ready(function() {
    console.log('Application initialized');
    
    // Initialize components
    MyApp.navigation.init();
    MyApp.forms.init();
});

Component-Based JavaScript:

// static/js/components/navigation.js

MyApp.navigation = {
    init: function() {
        this.bindEvents();
        this.setActiveNavItem();
    },
    
    bindEvents: function() {
        // Mobile menu toggle
        const mobileToggle = document.querySelector('.mobile-menu-toggle');
        const mobileMenu = document.querySelector('.mobile-menu');
        
        if (mobileToggle && mobileMenu) {
            mobileToggle.addEventListener('click', function() {
                mobileMenu.classList.toggle('active');
            });
        }
        
        // Dropdown menus
        const dropdowns = document.querySelectorAll('.dropdown');
        dropdowns.forEach(function(dropdown) {
            const toggle = dropdown.querySelector('.dropdown-toggle');
            const menu = dropdown.querySelector('.dropdown-menu');
            
            toggle.addEventListener('click', function(e) {
                e.preventDefault();
                menu.classList.toggle('show');
            });
        });
    },
    
    setActiveNavItem: function() {
        const currentPath = window.location.pathname;
        const navLinks = document.querySelectorAll('.nav-link');
        
        navLinks.forEach(function(link) {
            if (link.getAttribute('href') === currentPath) {
                link.classList.add('active');
            }
        });
    }
};

AJAX and Django Integration

// static/js/components/forms.js

MyApp.forms = {
    init: function() {
        this.bindAjaxForms();
        this.bindFormValidation();
    },
    
    bindAjaxForms: function() {
        const ajaxForms = document.querySelectorAll('.ajax-form');
        
        ajaxForms.forEach(function(form) {
            form.addEventListener('submit', function(e) {
                e.preventDefault();
                
                const formData = new FormData(form);
                const url = form.action || window.location.href;
                
                MyApp.utils.ajax(url, {
                    method: 'POST',
                    body: formData
                })
                .then(response => response.json())
                .then(data => {
                    if (data.success) {
                        MyApp.forms.showMessage('Success!', 'success');
                        form.reset();
                    } else {
                        MyApp.forms.showErrors(data.errors);
                    }
                })
                .catch(error => {
                    MyApp.forms.showMessage('An error occurred', 'error');
                });
            });
        });
    },
    
    showMessage: function(message, type) {
        const alertDiv = document.createElement('div');
        alertDiv.className = `alert alert-${type}`;
        alertDiv.textContent = message;
        
        const container = document.querySelector('.messages-container');
        if (container) {
            container.appendChild(alertDiv);
            
            // Auto-remove after 5 seconds
            setTimeout(function() {
                alertDiv.remove();
            }, 5000);
        }
    },
    
    showErrors: function(errors) {
        Object.keys(errors).forEach(function(field) {
            const fieldElement = document.querySelector(`[name="${field}"]`);
            if (fieldElement) {
                const errorDiv = document.createElement('div');
                errorDiv.className = 'field-error';
                errorDiv.textContent = errors[field][0];
                
                fieldElement.parentNode.appendChild(errorDiv);
            }
        });
    }
};

Image Optimization

Responsive Images

{% load static %}

<!-- Responsive image with multiple sources -->
<picture>
    <source media="(max-width: 480px)" 
            srcset="{% static 'images/hero-mobile.webp' %} 1x,
                    {% static 'images/hero-mobile@2x.webp' %} 2x">
    <source media="(max-width: 768px)" 
            srcset="{% static 'images/hero-tablet.webp' %} 1x,
                    {% static 'images/hero-tablet@2x.webp' %} 2x">
    <source srcset="{% static 'images/hero-desktop.webp' %} 1x,
                    {% static 'images/hero-desktop@2x.webp' %} 2x">
    <img src="{% static 'images/hero-desktop.jpg' %}" 
         alt="Hero Image" 
         class="hero-image">
</picture>

<!-- Simple responsive image -->
<img src="{% static 'images/product.jpg' %}"
     srcset="{% static 'images/product-480.jpg' %} 480w,
             {% static 'images/product-768.jpg' %} 768w,
             {% static 'images/product-1200.jpg' %} 1200w"
     sizes="(max-width: 480px) 480px,
            (max-width: 768px) 768px,
            1200px"
     alt="Product Image"
     loading="lazy">

CSS Background Images

/* Responsive background images */
.hero-section {
    background-image: url('../images/hero-mobile.jpg');
    background-size: cover;
    background-position: center;
    min-height: 300px;
}

@media (min-width: 768px) {
    .hero-section {
        background-image: url('../images/hero-tablet.jpg');
        min-height: 400px;
    }
}

@media (min-width: 1200px) {
    .hero-section {
        background-image: url('../images/hero-desktop.jpg');
        min-height: 500px;
    }
}

/* High DPI displays */
@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
    .hero-section {
        background-image: url('../images/hero-desktop@2x.jpg');
    }
}

Font Management

Web Fonts

/* static/css/fonts.css */

/* Google Fonts */
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');

/* Custom fonts */
@font-face {
    font-family: 'CustomFont';
    src: url('../fonts/custom-font.woff2') format('woff2'),
         url('../fonts/custom-font.woff') format('woff'),
         url('../fonts/custom-font.ttf') format('truetype');
    font-weight: normal;
    font-style: normal;
    font-display: swap;
}

/* Font usage */
body {
    font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.custom-heading {
    font-family: 'CustomFont', serif;
}

Icon Fonts

{% load static %}

<!-- Font Awesome -->
<link rel="stylesheet" href="{% static 'css/fontawesome.min.css' %}">

<!-- Custom icon font -->
<link rel="stylesheet" href="{% static 'fonts/icons.css' %}">

<!-- Usage -->
<i class="fas fa-user"></i>
<i class="icon-custom-search"></i>

Development vs Production

Development Settings

# settings/development.py

# Serve static files during development
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage'

# Debug mode serves static files automatically
DEBUG = True

# Additional static file directories for development
STATICFILES_DIRS = [
    BASE_DIR / 'static',
    BASE_DIR / 'node_modules',  # For npm packages
]

Production Settings

# settings/production.py

# Optimized static file storage
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'

# CDN integration
STATIC_URL = 'https://cdn.example.com/static/'

# Compressed static files
STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

Collecting Static Files

# Collect all static files for production
python manage.py collectstatic

# Collect with no input prompts
python manage.py collectstatic --noinput

# Clear existing files first
python manage.py collectstatic --clear --noinput

Performance Optimization

File Compression

# settings.py with WhiteNoise
MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'whitenoise.middleware.WhiteNoiseMiddleware',  # Add this
    # ... other middleware
]

STATICFILES_STORAGE = 'whitenoise.storage.CompressedManifestStaticFilesStorage'

# WhiteNoise settings
WHITENOISE_USE_FINDERS = True
WHITENOISE_AUTOREFRESH = True  # Development only
WHITENOISE_MAX_AGE = 31536000  # 1 year cache

CSS and JavaScript Minification

{% load static %}

<!-- Development -->
{% if debug %}
    <link rel="stylesheet" href="{% static 'css/main.css' %}">
    <script src="{% static 'js/main.js' %}"></script>
{% else %}
    <!-- Production - minified files -->
    <link rel="stylesheet" href="{% static 'css/main.min.css' %}">
    <script src="{% static 'js/main.min.js' %}"></script>
{% endif %}

Caching Headers

# settings.py
STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.ManifestStaticFilesStorage'

# Custom storage with longer cache times
class CustomStaticFilesStorage(ManifestStaticFilesStorage):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.max_age = 31536000  # 1 year

Django's static files system provides powerful tools for managing assets efficiently across development and production environments. Proper organization, optimization, and caching strategies ensure fast-loading, maintainable web applications.