In Django, a project is the entire web application, while an app is a specific component that handles a particular functionality. Apps are designed to be reusable and modular, following Django's philosophy of loose coupling and high cohesion.
A Django project is the container for your entire web application:
A Django app is a Python package that provides specific functionality:
Real-world Example:
ecommerce_project/ # Project
├── products/ # App: Product catalog
├── orders/ # App: Order management
├── users/ # App: User authentication
├── payments/ # App: Payment processing
└── reviews/ # App: Product reviews
Ensure you're in your Django project directory (where manage.py is located):
cd mysite
ls -la
# You should see manage.py in the output
Use Django's startapp command to create a new app:
python manage.py startapp blog
This creates a new directory structure:
blog/
├── __init__.py # Makes it a Python package
├── admin.py # Admin interface configuration
├── apps.py # App configuration
├── migrations/ # Database migration files
│ └── __init__.py
├── models.py # Data models
├── tests.py # Unit tests
└── views.py # View functions/classes
Add your new app to the INSTALLED_APPS setting in mysite/settings.py:
# mysite/settings.py
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'blog', # Add your app here
]
Why Register Apps?
models.py - Data LayerDefines your database structure and business logic:
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.SlugField(unique=True)
description = models.TextField(blank=True)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = "categories"
ordering = ['name']
def __str__(self):
return self.name
def get_absolute_url(self):
return reverse('blog:category_detail', kwargs={'slug': self.slug})
class Post(models.Model):
STATUS_CHOICES = [
('draft', 'Draft'),
('published', 'Published'),
('archived', 'Archived'),
]
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_posts')
category = models.ForeignKey(Category, on_delete=models.CASCADE, related_name='posts')
content = models.TextField()
excerpt = models.TextField(max_length=300, blank=True)
status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='draft')
featured_image = models.ImageField(upload_to='blog/images/', blank=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published_at = models.DateTimeField(null=True, blank=True)
class Meta:
ordering = ['-created_at']
indexes = [
models.Index(fields=['status', 'published_at']),
models.Index(fields=['category', 'status']),
]
def __str__(self):
return self.title
def get_absolute_url(self):
return reverse('blog:post_detail', kwargs={'slug': self.slug})
@property
def is_published(self):
return self.status == 'published'
views.py - Logic LayerHandles request processing and response generation:
# blog/views.py
from django.shortcuts import render, get_object_or_404
from django.views.generic import ListView, DetailView
from django.db.models import Q
from .models import Post, Category
def home(request):
"""Simple function-based view for homepage"""
recent_posts = Post.objects.filter(status='published')[:5]
featured_posts = Post.objects.filter(
status='published',
featured_image__isnull=False
)[:3]
context = {
'recent_posts': recent_posts,
'featured_posts': featured_posts,
}
return render(request, 'blog/home.html', context)
class PostListView(ListView):
"""Class-based view for post listing"""
model = Post
template_name = 'blog/post_list.html'
context_object_name = 'posts'
paginate_by = 10
def get_queryset(self):
return Post.objects.filter(status='published').select_related('author', 'category')
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context['categories'] = Category.objects.all()
return context
class PostDetailView(DetailView):
"""Class-based view for individual post"""
model = Post
template_name = 'blog/post_detail.html'
context_object_name = 'post'
def get_queryset(self):
return Post.objects.filter(status='published').select_related('author', 'category')
def search_posts(request):
"""Search functionality"""
query = request.GET.get('q')
posts = []
if query:
posts = Post.objects.filter(
Q(title__icontains=query) |
Q(content__icontains=query) |
Q(excerpt__icontains=query),
status='published'
).select_related('author', 'category')
context = {
'posts': posts,
'query': query,
}
return render(request, 'blog/search.html', context)
admin.py - Admin InterfaceConfigures the Django admin interface:
# blog/admin.py
from django.contrib import admin
from .models import Category, Post
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'created_at']
prepopulated_fields = {'slug': ('name',)}
search_fields = ['name', 'description']
list_filter = ['created_at']
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'status', 'created_at']
list_filter = ['status', 'category', 'created_at', 'author']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
date_hierarchy = 'created_at'
raw_id_fields = ['author']
fieldsets = (
('Content', {
'fields': ('title', 'slug', 'author', 'category', 'content', 'excerpt')
}),
('Media', {
'fields': ('featured_image',)
}),
('Publishing', {
'fields': ('status', 'published_at'),
'classes': ('collapse',)
}),
)
def save_model(self, request, obj, form, change):
if not change: # If creating new object
obj.author = request.user
super().save_model(request, obj, form, change)
apps.py - App ConfigurationConfigures app-specific settings:
# blog/apps.py
from django.apps import AppConfig
class BlogConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'blog'
verbose_name = 'Blog Management'
def ready(self):
"""Import signal handlers when app is ready"""
import blog.signals
Create a urls.py file in your app directory:
# blog/urls.py
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.home, name='home'),
path('posts/', views.PostListView.as_view(), name='post_list'),
path('posts/<slug:slug>/', views.PostDetailView.as_view(), name='post_detail'),
path('category/<slug:slug>/', views.CategoryDetailView.as_view(), name='category_detail'),
path('search/', views.search_posts, name='search'),
]
Include app URLs in your project's main urls.py:
# mysite/urls.py
from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
path('admin/', admin.site.urls),
path('blog/', include('blog.urls')),
path('', include('blog.urls')), # Root URL points to blog
]
# Serve media files during development
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
Create template directories:
mkdir -p blog/templates/blog
<!-- blog/templates/blog/base.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}My Blog{% endblock %}</title>
{% load static %}
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="{% static 'blog/css/style.css' %}">
{% block extra_css %}{% endblock %}
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<div class="container">
<a class="navbar-brand" href="{% url 'blog:home' %}">My Blog</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="{% url 'blog:home' %}">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="{% url 'blog:post_list' %}">Posts</a>
</li>
</ul>
<form class="d-flex" method="get" action="{% url 'blog:search' %}">
<input class="form-control me-2" type="search" name="q" placeholder="Search posts...">
<button class="btn btn-outline-light" type="submit">Search</button>
</form>
</div>
</div>
</nav>
<main class="container mt-4">
{% if messages %}
{% for message in messages %}
<div class="alert alert-{{ message.tags }} alert-dismissible fade show" role="alert">
{{ message }}
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
{% endfor %}
{% endif %}
{% block content %}{% endblock %}
</main>
<footer class="bg-dark text-light mt-5 py-4">
<div class="container">
<div class="row">
<div class="col-md-6">
<p>© 2024 My Blog. All rights reserved.</p>
</div>
<div class="col-md-6 text-end">
<p>Built with Django</p>
</div>
</div>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
{% block extra_js %}{% endblock %}
</body>
</html>
<!-- blog/templates/blog/home.html -->
{% extends 'blog/base.html' %}
{% load static %}
{% block title %}Welcome to My Blog{% endblock %}
{% block content %}
<div class="row">
<div class="col-lg-8">
<section class="mb-5">
<h1 class="display-4 mb-4">Welcome to My Blog</h1>
<p class="lead">Discover amazing content and insights on various topics.</p>
</section>
{% if featured_posts %}
<section class="mb-5">
<h2 class="h3 mb-4">Featured Posts</h2>
<div class="row">
{% for post in featured_posts %}
<div class="col-md-4 mb-4">
<div class="card h-100">
{% if post.featured_image %}
<img src="{{ post.featured_image.url }}" class="card-img-top" alt="{{ post.title }}">
{% endif %}
<div class="card-body">
<h5 class="card-title">
<a href="{{ post.get_absolute_url }}" class="text-decoration-none">
{{ post.title }}
</a>
</h5>
<p class="card-text">{{ post.excerpt|truncatewords:20 }}</p>
<small class="text-muted">
By {{ post.author.get_full_name|default:post.author.username }}
on {{ post.created_at|date:"M d, Y" }}
</small>
</div>
</div>
</div>
{% endfor %}
</div>
</section>
{% endif %}
<section>
<h2 class="h3 mb-4">Recent Posts</h2>
{% for post in recent_posts %}
<article class="mb-4 pb-4 border-bottom">
<h3 class="h4">
<a href="{{ post.get_absolute_url }}" class="text-decoration-none">
{{ post.title }}
</a>
</h3>
<p class="text-muted mb-2">
<small>
By {{ post.author.get_full_name|default:post.author.username }}
in {{ post.category.name }}
on {{ post.created_at|date:"M d, Y" }}
</small>
</p>
<p>{{ post.excerpt|default:post.content|truncatewords:30 }}</p>
<a href="{{ post.get_absolute_url }}" class="btn btn-primary btn-sm">Read More</a>
</article>
{% empty %}
<p>No posts available yet.</p>
{% endfor %}
</section>
</div>
<div class="col-lg-4">
<aside>
<div class="card mb-4">
<div class="card-header">
<h4 class="card-title">About</h4>
</div>
<div class="card-body">
<p>Welcome to my blog where I share insights about web development, Django, and technology.</p>
</div>
</div>
<div class="card">
<div class="card-header">
<h4 class="card-title">Quick Links</h4>
</div>
<div class="card-body">
<ul class="list-unstyled">
<li><a href="{% url 'blog:post_list' %}" class="text-decoration-none">All Posts</a></li>
<li><a href="{% url 'admin:index' %}" class="text-decoration-none">Admin</a></li>
</ul>
</div>
</div>
</aside>
</div>
</div>
{% endblock %}
After creating your models, generate and apply migrations:
# Generate migration files
python manage.py makemigrations blog
# Apply migrations to database
python manage.py migrate
Migration Output:
Migrations for 'blog':
blog/migrations/0001_initial.py
- Create model Category
- Create model Post
Create an admin user to access the Django admin interface:
python manage.py createsuperuser
Follow the prompts to set username, email, and password.
python manage.py runserver
Each app should have one clear purpose:
# Good: Focused apps
products/ # Product catalog management
orders/ # Order processing
payments/ # Payment handling
reviews/ # Product reviews
# Bad: Mixed responsibilities
shop/ # Everything e-commerce related
# App names should be:
# - Plural nouns (products, orders, users)
# - Descriptive (blog, not app1)
# - Lowercase with underscores (user_profiles, not UserProfiles)
# Good examples
blog/
products/
user_profiles/
order_management/
# Avoid
app1/
stuff/
MyApp/
# Keep related models in the same app
class Category(models.Model):
name = models.CharField(max_length=100)
class Product(models.Model):
category = models.ForeignKey(Category, on_delete=models.CASCADE)
name = models.CharField(max_length=200)
class ProductImage(models.Model):
product = models.ForeignKey(Product, on_delete=models.CASCADE)
image = models.ImageField(upload_to='products/')
Always use app namespaces:
# blog/urls.py
app_name = 'blog' # Important for namespacing
urlpatterns = [
path('', views.home, name='home'),
path('posts/', views.PostListView.as_view(), name='post_list'),
]
# In templates, use namespaced URLs
<a href="{% url 'blog:home' %}">Home</a>
<a href="{% url 'blog:post_list' %}">Posts</a>
Problem: App not working after creation
Solution: Add app to INSTALLED_APPS in settings.py
Problem: ModuleNotFoundError when importing models
Solution: Ensure proper app structure and __init__.py files
Problem: Models not reflected in database
Solution: Always run makemigrations and migrate after model changes
Problem: TemplateDoesNotExist error
Solution: Check template directory structure and TEMPLATES setting
Problem: NoReverseMatch errors
Solution: Use proper URL namespacing and check URL patterns
After creating your first app:
Your first Django app is now complete and functional. This foundation provides a solid starting point for building more complex web applications with Django's powerful features.
Creating Your First Django Project
Creating a Django project is the first step in building web applications with Django. This guide covers project creation, initial configuration, and best practices for setting up a professional Django development environment.
Your First Django "Hello World"
Creating your first "Hello World" application in Django demonstrates the fundamental concepts of views, URLs, and templates. This guide walks you through building a simple but complete Django application from scratch.