Django is a high-level web framework that facilitates the developmant of interactive websites by handling web requests, database operations, and user management. This guide details the creation of a Learning Log application—an online journal system for tracking knowledge on various subjects.
Project Initialization
To begin, establish a project with a clear specification and a dedicated virtual environment to isolate dependencies.
Project Specification
Define the application's goals and functionality. The Learning Log will allow users to register, log in, create topics, and add log entries related to those topics.
Virtual Environment Setup
Create an isolated Python environment to manage project-specific packages.
python -m venv learning_log_env
Activate the virtual environment:
source learning_log_env/bin/activate
On Windows, use:
learning_log_env\Scripts\activate
Installing Django
With the environment active, install Django.
pip install django
Creating the Django Project
Generate the project structure.
django-admin startproject learning_log_project .
The dot ensures proper directory configuration for deployment.
Database Setup
Django uses a database to store project information. Initialize it with:
python manage.py migrate
This command creates a SQLite database file, db.sqlite3, suitable for development.
Runing the Development Server
Start the server to verify the project.
python manage.py runserver
Visit http://127.0.0.1:8000/ in a browser. If port 8000 is busy, specify another, e.g., runserver 8001.
Creating an Application
Django projects consist of applications. Create one named log_entries.
python manage.py startapp log_entries
This creates directories and files including models.py, admin.py, and views.py.
Defining Data Models
Models define the data structure. In log_entries/models.py:
from django.db import models
class Subject(models.Model):
"""A topic the user is learning about."""
name = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
def __str__(self):
return self.name
class JournalEntry(models.Model):
"""An entry about a specific subject."""
subject = models.ForeignKey(Subject, on_delete=models.CASCADE)
content = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name_plural = 'journal_entries'
def __str__(self):
return self.content[:50] + '...'
Activating Models
Add the application to the project's INSTALLED_APPS in learning_log_project/settings.py.
INSTALLED_APPS = [
'log_entries',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
]
Create and apply database migrations.
python manage.py makemigrations log_entries
python manage.py migrate
Django Admin Interface
Register models with the admin site in log_entries/admin.py.
from django.contrib import admin
from .models import Subject, JournalEntry
admin.site.register(Subject)
admin.site.register(JournalEntry)
Create a superuser to access the admin site.
python manage.py createsuperuser
Interactive Django Shell
Use the Django shell to interact with data.
python manage.py shell
Example queries:
from log_entries.models import Subject
Subject.objects.all()
for s in Subject.objects.all():
print(s.id, s.name)
Creating Web Pages
The process involves defining URLs, writing views, and creating templates.
URL Configuration
First, include the app's URLs in the project's main urls.py.
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('log_entries.urls')),
]
Create log_entries/urls.py:
from django.urls import path
from . import views
app_name = 'log_entries'
urlpatterns = [
path('', views.homepage, name='home'),
]
View Functions
Define the view in log_entries/views.py.
from django.shortcuts import render
def homepage(request):
"""Render the application's homepage."""
return render(request, 'log_entries/home.html')
Templates
Create a template at log_entries/templates/log_entries/home.html.
<!DOCTYPE html>
<html>
<head>
<title>Learning Log</title>
</head>
<body>
<h1>Learning Log</h1>
<p>Track your learning on any topic.</p>
</body>
</html>
Building Additional Pages
Extend the application to list subjects and show entries for a specific subject.
Template Inheritance
Create a base template log_entries/templates/log_entries/base.html.
<!DOCTYPE html>
<html>
<head>
<title>Learning Log</title>
</head>
<body>
<p><a href="{% url 'log_entries:home' %}">Home</a></p>
{% block main_content %}{% endblock %}
</body>
</html>
Child templates extend this base.
Listing All Subjects
Update urls.py:
urlpatterns = [
path('', views.homepage, name='home'),
path('subjects/', views.subject_list, name='subject_list'),
]
Create the view:
def subject_list(request):
"""Display all subjects."""
subjects = Subject.objects.order_by('created_at')
context = {'subjects': subjects}
return render(request, 'log_entries/subjects.html', context)
Create the template subjects.html:
{% extends 'log_entries/base.html' %}
{% block main_content %}
<h2>Subjects</h2>
<ul>
{% for subject in subjects %}
<li>{{ subject.name }}</li>
{% empty %}
<li>No subjects have been added yet.</li>
{% endfor %}
</ul>
{% endblock %}
Displaying Entries for a Subject
Add a URL pattern for a specific subject.
path('subjects/<int:subject_id>/', views.subject_detail, name='subject_detail'),
Create the view:
def subject_detail(request, subject_id):
"""Show a single subject and all its entries."""
subject = Subject.objects.get(id=subject_id)
entries = subject.journalentry_set.order_by('created_at')
context = {'subject': subject, 'entries': entries}
return render(request, 'log_entries/subject_detail.html', context)
Create the template subject_detail.html:
{% extends 'log_entries/base.html' %}
{% block main_content %}
<h2>{{ subject.name }}</h2>
<p>Entries:</p>
<ul>
{% for entry in entries %}
<li>
<p>{{ entry.created_at|date:'M d, Y H:i' }}</p>
<p>{{ entry.content|linebreaks }}</p>
</li>
{% empty %}
<li>No entries for this subject yet.</li>
{% endfor %}
</ul>
{% endblock %}
Update the subjects.html template to link to each subject's detail page.
<li>
<a href="{% url 'log_entries:subject_detail' subject.id %}">{{ subject.name }}</a>
</li>
User Data Input
Create forms to allow users to add new subjects and entries.
Form for Adding a Subject
Create log_entries/forms.py.
from django import forms
from .models import Subject, JournalEntry
class SubjectForm(forms.ModelForm):
class Meta:
model = Subject
fields = ['name']
labels = {'name': ''}
class EntryForm(forms.ModelForm):
class Meta:
model = JournalEntry
fields = ['content']
labels = {'content': ''}
widgets = {'content': forms.Textarea(attrs={'cols': 80})}
View for New Subject
Add a URL and view.
# urls.py
path('new_subject/', views.new_subject, name='new_subject'),
# views.py
from django.shortcuts import render, redirect
from .forms import SubjectForm
def new_subject(request):
"""Add a new subject."""
if request.method != 'POST':
form = SubjectForm()
else:
form = SubjectForm(data=request.POST)
if form.is_valid():
form.save()
return redirect('log_entries:subject_list')
context = {'form': form}
return render(request, 'log_entries/new_subject.html', context)
Create the template new_subject.html.
{% extends 'log_entries/base.html' %}
{% block main_content %}
<h2>Add a new subject</h2>
<form action="{% url 'log_entries:new_subject' %}" method="post">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">Add Subject</button>
</form>
{% endblock %}
Add a link to this page from the subject list template.
View for New Entry
Add a URL and view for creating entries linked to a subject.
# urls.py
path('new_entry/<int:subject_id>/', views.new_entry, name='new_entry'),
# views.py
def new_entry(request, subject_id):
"""Add a new entry for a particular subject."""
subject = Subject.objects.get(id=subject_id)
if request.method != 'POST':
form = EntryForm()
else:
form = EntryForm(data=request.POST)
if form.is_valid():
new_entry_obj = form.save(commit=False)
new_entry_obj.subject = subject
new_entry_obj.save()
return redirect('log_entries:subject_detail', subject_id=subject_id)
context = {'subject': subject, 'form': form}
return render(request, 'log_entries/new_entry.html', context)
Create the template new_entry.html and add a link from the subject detail page.
Editing an Existing Entry
Add functionality to edit entries.
# urls.py
path('edit_entry/<int:entry_id>/', views.edit_entry, name='edit_entry'),
# views.py
def edit_entry(request, entry_id):
"""Edit an existing entry."""
entry = JournalEntry.objects.get(id=entry_id)
subject = entry.subject
if request.method != 'POST':
form = EntryForm(instance=entry)
else:
form = EntryForm(instance=entry, data=request.POST)
if form.is_valid():
form.save()
return redirect('log_entries:subject_detail', subject_id=subject.id)
context = {'entry': entry, 'subject': subject, 'form': form}
return render(request, 'log_entries/edit_entry.html', context)
Create the template edit_entry.html and add an edit link next to each entry in the subject detail page.
User Account Management
Create a separate app for user accounts.
python manage.py startapp user_accounts
Add it to INSTALLED_APPS and include its URLs in the project's main urls.py.
path('accounts/', include('user_accounts.urls')),
Login Page
Create user_accounts/urls.py to use Django's built-in authentication views.
from django.urls import path, include
app_name = 'user_accounts'
urlpatterns = [
path('', include('django.contrib.auth.urls')),
]
Create a login template at user_accounts/templates/registration/login.html.
{% extends 'log_entries/base.html' %}
{% block main_content %}
<h2>Log in</h2>
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
<form method="post" action="{% url 'user_accounts:login' %}">
{% csrf_token %}
{{ form.as_p }}
<button name="submit">Log in</button>
<input type="hidden" name="next" value="{% url 'log_entries:home' %}" />
</form>
{% endblock %}
Update the base template to show login/logout links conditionally.
<p>
<a href="{% url 'log_entries:home' %}">Home</a> -
<a href="{% url 'log_entries:subject_list' %}">Subjects</a> -
{% if user.is_authenticated %}
Hello, {{ user.username }}.
<a href="{% url 'user_accounts:logout' %}">Log out</a>
{% else %}
<a href="{% url 'user_accounts:login' %}">Log in</a>
{% endif %}
</p>
Create a logout confirmation template at user_accounts/templates/registration/logged_out.html.
Registration Page
Create a custom view for registration in user_accounts/views.py.
from django.shortcuts import render, redirect
from django.contrib.auth import login
from django.contrib.auth.forms import UserCreationForm
def register(request):
"""Register a new user."""
if request.method != 'POST':
form = UserCreationForm()
else:
form = UserCreationForm(data=request.POST)
if form.is_valid():
new_user = form.save()
login(request, new_user)
return redirect('log_entries:home')
context = {'form': form}
return render(request, 'registration/register.html', context)
Add a URL pattern for registration.
# user_accounts/urls.py
from django.urls import path
from . import views
urlpatterns = [
path('', include('django.contrib.auth.urls')),
path('register/', views.register, name='register'),
]
Create the registration template register.html in the registration directory and add a registration link to the base template for non-authenticated users.
Restricting Access
Use the @login_required decorator to protect views.
from django.contrib.auth.decorators import login_required
@login_required
def subject_list(request):
# ... view code
Apply this decorator to all views that should require login, except the home page and registration pages.
In settings.py, set the login URL.
LOGIN_URL = 'user_accounts:login'
Associating Data with Users
Modify the Subject model to include an owner.
from django.contrib.auth.models import User
class Subject(models.Model):
name = models.CharField(max_length=200)
created_at = models.DateTimeField(auto_now_add=True)
owner = models.ForeignKey(User, on_delete=models.CASCADE)
# ...
Run migrations, providing a default user ID for existing subjects during the migration process.
Modify views to filter subjects by the current user.
@login_required
def subject_list(request):
subjects = Subject.objects.filter(owner=request.user).order_by('created_at')
context = {'subjects': subjects}
return render(request, 'log_entries/subjects.html', context)
Protect the subject detail and edit entry views to ensure users can only access their own data.
from django.http import Http404
@login_required
def subject_detail(request, subject_id):
subject = Subject.objects.get(id=subject_id)
if subject.owner != request.user:
raise Http404
# ...
Update the new_subject view to assign the owner automatically.
@login_required
def new_subject(request):
if request.method != 'POST':
form = SubjectForm()
else:
form = SubjectForm(data=request.POST)
if form.is_valid():
new_subject_obj = form.save(commit=False)
new_subject_obj.owner = request.user
new_subject_obj.save()
return redirect('log_entries:subject_list')
context = {'form': form}
return render(request, 'log_entries/new_subject.html', context)
Styling with Bootstrap
Install django-bootstrap4.
pip install django-bootstrap4
Add it to INSTALLED_APPS.
INSTALLED_APPS = [
# ...
'bootstrap4',
# ...
]
Update the base template and all other templates to use Bootstrap classes for responsive design and improved aesthetics. Load Bootstrap tags and use its components like navbars, jumbotrons, cards, and form styling.
Refactor templates to use Bootstrap's grid system and utility classes, replacing basic HTML with styled components.