This guide walks through setting up and running a Django web application from scratch on Windows, covering environment configuration, app creation, template rendering, form handling, user authentication, database management, static files, and deployment to Render. It is intended for developers who are new to Django or need a step-by-step reference for building their first project.
- Prerequisites
- Setting Up a Virtual Environment
- Installing Django
- Starting the Development Server
- Creating a Django App
- Registering the App
- Templates
- Views
- URL Configuration
- Template Inheritance (Blocks)
- Form Submission
- Backend Validation & Flash Messages
- Redirect and Resolve
- Database Models
- Django Admin Panel
- User Authentication
- Login-Required Protection
- Static Files (CSS, JS, Images)
- Media & File Uploads
- Comments in Python
- .gitignore
- Deploying to Render
Open Command Prompt and run:
python --versionIf Python is not installed, download the latest version from python.org and run the installer. Check all boxes during installation, then confirm the installation by running the command above again.
pip --versionIt is good practice to always use a virtual environment in Python projects. A virtual environment creates an isolated space for each project's dependencies, preventing version conflicts between projects.
pip install virtualenvRun this in your project terminal (e.g., VS Code):
python -m venv envNote: You can name your environment anything, but
envorvenvare recommended because.gitignoretemplates automatically exclude them. If you use a different name, you will need to add it manually to.gitignore.
Windows:
env\Scripts\activateMac/Linux:
source env/bin/activateNote: If Windows blocks the activation script, switch your terminal from PowerShell to Command Prompt and run the command again.
You will need to reactivate the virtual environment each time you return to the project.
Once the virtual environment is active, install packages as needed. Example:
pip install djangopip install djangoCreate a new Django project:
django-admin startproject projectname .Note: The
.at the end is important because without it, Django creates a nested project folder.
Do not modify the manage.py file.
python manage.py runserverOpen the port shown in the terminal in your browser. To stop the server, press Ctrl + C.
Security tip: Change the default
admin/path inurls.pyto something less predictable to reduce exposure to automated attacks:urlpatterns = [ path('dashboard/', admin.site.urls), ]
python manage.py startapp appname- Open
settings.pyin your project folder. - Add your app name to the
INSTALLED_APPSlist:
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'appname', # your app
]- Create a
urls.pyfile inside your app folder and add:
from django.urls import path
urlpatterns = []- In the project's
urls.py, importincludeand add a path for your app:
from django.urls import path, include
urlpatterns = [
path('', include('appname.urls')), # handles all root-level requests
path('appname/', include('appname.urls')), # handles requests prefixed with appname/
]In settings.py, locate the TEMPLATES configuration block. The recommended approach is to create a templates folder inside each app directory, so each app manages its own templates.
Create HTML files inside the templates folder.
Navigate to views.py in your app folder.
Django supports two types of views:
Easier to learn and suitable for most use cases:
def homepage(request):
return render(request, 'home.html')More concise for complex views:
from django.views import View
class ContactView(View):
def get(self, request):
return render(request, 'contact.html')
def post(self, request):
return render(request, 'contact.html')In your app's urls.py:
from django.urls import path
from appname.views import homepage, aboutpage
urlpatterns = [
path('', homepage, name='home'),
path('about', aboutpage, name='about'),
]from appname.views import ContactView
urlpatterns = [
path('contact', ContactView.as_view(), name='contact'),
]Use the {% url %} tag to reference named URL patterns:
<a href="{% url 'about' %}">About</a>Create a base.html file that contains elements common to all pages (e.g., navigation, header, footer). Use blocks to define areas that child pages can override:
<!-- base.html -->
<title>{% block title %}{% endblock title %}</title>
<body>
{% block main %}{% endblock main %}
</body>In child pages, extend the base and fill in the blocks:
{% extends 'base.html' %}
{% block title %}Homepage{% endblock title %}
{% block main %}
<h1>Welcome</h1>
<a href="{% url 'about' %}">About</a>
{% endblock main %}To include one template inside another (e.g., a testimonial section on the homepage):
{% include 'testimony.html' %}Use the POST method for form submissions to keep data out of the URL. Add {% csrf_token %} inside the form to prevent cross-site request forgery:
<form method="POST">
{% csrf_token %}
<input type="text" name="fullname">
<button type="submit">Submit</button>
</form>Extract submitted data in views.py using .get() to avoid errors when a field is missing:
def contactpage(request):
if request.method == "POST":
fullname = request.POST.get("fullname")
email = request.POST.get("email")
about = request.POST.get("about")
print(f'Submitted by: {fullname}, Message: {about}')
return render(request, 'contact.html')Django's built-in messages framework lets you send feedback from the backend to the frontend. It is included by default in INSTALLED_APPS.
In views.py:
from django.contrib import messages
def contactpage(request):
if request.method == "POST":
fullname = request.POST.get("fullname")
email = request.POST.get("email")
about = request.POST.get("about")
if not fullname:
messages.error(request, "Please provide your full name.")
return render(request, 'contact.html')
if not email or not about:
messages.error(request, "All fields are required.")
return render(request, 'contact.html')
if len(fullname) < 5:
messages.error(request, "Name is too short.")
return render(request, 'contact.html')
messages.success(request, "Details submitted successfully.")
return render(request, 'contact.html')Display messages in base.html so they appear on every page:
<div class="error_container">
{% for msg in messages %}
<p>{{ msg }}</p>
{% endfor %}
</div>Note: Django uses Jinja-style template syntax, similar to how React uses JSX.
Import redirect and resolve_url from Django shortcuts to navigate users after a successful form submission:
from django.shortcuts import render, redirect, resolve_url
return redirect(homepage)
# or, if the view is in a different file:
return redirect(resolve_url('home'))In models.py, create a class that extends models.Model:
from django.db import models
import uuid
class ContactMessage(models.Model):
fullname = models.CharField(max_length=250)
email = models.EmailField()
about = models.TextField()
created_at = models.DateTimeField(auto_now_add=True) # set on creation
updated_at = models.DateTimeField(auto_now=True) # updated on save
def __str__(self):
return f'Name: {self.fullname} | Email: {self.email}'Note: Django automatically adds an
idfield to every model.
To use a non-predictable UUID as the primary key:
id = models.UUIDField(default=uuid.uuid4, primary_key=True, editable=False)To link a model to a user (foreign key relationship):
from django.contrib.auth.models import User
user = models.ForeignKey(User, on_delete=models.CASCADE)Run these two commands whenever you create or modify a model:
python manage.py makemigrations
python manage.py migrateIn views.py, import the model and save submitted data:
from appname.models import ContactMessage
ContactMessage.objects.create(
fullname=fullname,
email=email,
about=about
)python manage.py checkNote: This command does not catch all errors but is useful for quick validation.
python manage.py createsuperuserFollow the prompts for username, email, and password. The password will not be visible as you type. If prompted about password strength, type y to bypass.
Django has three user types: SuperUser, Staff User, and Normal User.
In admin.py:
from django.contrib import admin
from appname.models import ContactMessage
admin.site.register(ContactMessage)Reload the admin panel in your browser to see the registered model.
from django.views import View
from django.contrib.auth.models import User
from django.contrib import messages
from django.shortcuts import render, redirect, resolve_url
class SignupView(View):
def get(self, request):
return render(request, 'signup.html')
def post(self, request):
username = request.POST.get("username")
email = request.POST.get("email")
firstname = request.POST.get("firstname")
lastname = request.POST.get("lastname")
password = request.POST.get("password")
if not all([username, email, firstname, lastname, password]):
messages.error(request, "All fields are required.")
return render(request, 'signup.html')
if len(password) < 8:
messages.error(request, "Password must be at least 8 characters.")
return render(request, 'signup.html')
username = username.lower()
email = email.lower()
if User.objects.filter(username=username).exists():
messages.error(request, "Username is already taken.")
return render(request, 'signup.html')
if User.objects.filter(email=email).exists():
messages.error(request, "An account with this email already exists.")
return render(request, 'signup.html')
user = User.objects.create(
username=username,
email=email,
first_name=firstname,
last_name=lastname
)
user.set_password(password)
user.save()
return redirect(resolve_url('home'))Note: Always set the password using
set_password(), saving it directly stores it as plain text, which is a security risk.
Add the path in urls.py:
from authapp.views import SignupView
urlpatterns = [
path('signup', SignupView.as_view(), name='signup'),
]from django.contrib.auth import login, logout, authenticate
def login_view(request):
if request.method == "POST":
next_page = request.GET.get('next')
username = request.POST.get("username")
password = request.POST.get("password")
if not username or not password:
messages.error(request, "All fields are required.")
return render(request, 'login.html')
username = username.lower()
user_exists = User.objects.filter(username=username).first()
if not user_exists:
messages.error(request, "Invalid login credentials.")
return render(request, 'login.html')
user = authenticate(username=username, password=password)
if user:
login(request, user)
messages.success(request, "Login successful.")
return redirect(next_page or resolve_url('home'))
return render(request, 'login.html')def logout_view(request):
logout(request)
return render(request, 'login.html')Show different nav items based on whether the user is logged in:
{% if user.is_authenticated %}
<li><a href="{% url 'logout' %}">Logout</a></li>
{% else %}
<li><a href="{% url 'signup' %}">Sign Up</a></li>
<li><a href="{% url 'login' %}">Login</a></li>
{% endif %}Restrict views to authenticated users only.
from django.contrib.auth.decorators import login_required
@login_required
def homepage(request):
return render(request, 'home.html')from django.contrib.auth.mixins import LoginRequiredMixin
class DashboardView(LoginRequiredMixin, View):
def get(self, request):
return render(request, 'dashboard.html')In settings.py, set the login redirect URL:
LOGIN_URL = 'authapp/login'- Create a
staticfolder at the root level of your project. - In
settings.py, add afterSTATIC_URL:
import os
STATICFILES_DIRS = (os.path.join(BASE_DIR, 'static'),)- Link your stylesheet in templates. Load static at the top of the file:
{% load static %}
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="{% static 'css/style.css' %}">
</head>- Reference images:
{% load static %}
<img src="{% static 'assets/image.png' %}" alt="description" width="250px">For page-specific styles, define a block in base.html:
{% block styles %}{% endblock styles %}Then in child pages (remember to load static after extends):
{% extends 'base.html' %}
{% load static %}
{% block styles %}
<link rel="stylesheet" href="{% static 'css/about.css' %}">
{% endblock styles %}- Create a
mediafolder at the root level. - Add the following to
settings.py:
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')- In your model, use
upload_toto organize uploads into subfolders:
image = models.ImageField(upload_to="products/")Note: Handling image fields requires the Pillow library:
pip install pillow
# This is a single-line comment
"""
This is a multi-line comment
or docstring.
"""Create a .gitignore file at the root of your project. Visit gitignore.io, search for Django, copy the output, and paste it into your .gitignore file.
- Go to the Render Dashboard and click New + Web Service.
- Connect your GitHub repository.
- Set Runtime to Python.
Install Gunicorn (required by Render):
pip install gunicornGenerate a 'requirements.txt' file in your project root:
pip freeze > requirements.txtPush the changes to GitHub:
git add .
git commit -m "Added requirements.txt"
git pushUpdate ALLOWED_HOSTS with your Render domain:
ALLOWED_HOSTS = [
"your-app-name.onrender.com",
"localhost",
"127.0.0.1",
]Set DEBUG to False for production:
DEBUG = FalsePush the final changes:
git add .
git commit -m "Configured for Render deployment"
git push