Python Backend - Powerful Web Applications with Django and Flask
Complete guide to Python backend development using Django and Flask frameworks. Learn to build robust web applications, REST APIs, handle databases, and deploy scalable Python web services.
Introduction to Python Web Development
Python provides powerful frameworks for backend development. Django offers a batteries-included approach with rapid development features, while Flask provides flexibility with a micro-framework approach.
Flask - Lightweight Web Framework
Getting Started with Flask
# Install Flask
pip install flask
# app.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello():
return 'Hello, World!'
@app.route('/user/<name>')
def user(name):
return f'Hello, {name}!'
if __name__ == '__main__':
app.run(debug=True)
Routing and Views
from flask import Flask, request, jsonify, render_template, redirect, url_for, flash
app = Flask(__name__)
app.secret_key = 'dev-key-change-in-production'
# Basic routes
@app.route('/')
def home():
return render_template('home.html')
@app.route('/about')
def about():
return 'About Page'
# Dynamic routes
@app.route('/user/<int:user_id>')
def get_user(user_id):
return f'User ID: {user_id}'
@app.route('/blog/<slug>')
def blog_post(slug):
return f'Blog post: {slug}'
# HTTP methods
@app.route('/submit', methods=['POST'])
def submit():
if request.method == 'POST':
name = request.form['name']
email = request.form['email']
return f'Thank you {name}!'
# JSON API
@app.route('/api/users', methods=['GET', 'POST'])
def api_users():
if request.method == 'GET':
return jsonify({'users': ['Alice', 'Bob', 'Charlie']})
else:
data = request.get_json()
return jsonify({'message': 'User created', 'data': data})
Templates with Jinja2
from flask import render_template, flash
from markupsafe import Markup
@app.route('/dashboard')
def dashboard():
user = {'name': 'Alice', 'posts': 15}
recent_posts = ['Post 1', 'Post 2', 'Post 3']
return render_template('dashboard.html',
user=user,
posts=recent_posts,
title='Dashboard')
@app.route('/flashtest')
def flash_test():
flash('This is a flash message!')
return redirect(url_for('home'))
<!-- templates/dashboard.html -->
{% extends "base.html" %}
{% block title %}{{ title }}{% endblock %}
{% block content %}
<div class="dashboard">
<h1>Welcome {{ user.name }}!</h1>
{% with messages = get_flashed_messages() %}
{% if messages %}
<ul class="flash-messages">
{% for message in messages %}
<li>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
{% endwith %}
<p>You have {{ user.posts }} posts.</p>
<h2>Recent Posts</h2>
<ul>
{% for post in posts %}
<li>{{ post }}</li>
{% else %}
<li>No posts yet.</li>
{% endfor %}
</ul>
</div>
{% endblock %}
Database Integration with SQLAlchemy
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from datetime import datetime
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
db = SQLAlchemy(app)
# Define models
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(128))
posts = db.relationship('Post', backref='author', lazy=True)
def __repr__(self):
return f'<User {self.username}>'
class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(100), nullable=False)
content = db.Column(db.Text, nullable=False)
created_date = db.Column(db.DateTime, default=datetime.utcnow)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False)
@app.route('/users')
def list_users():
users = User.query.all()
return render_template('users.html', users=users)
@app.route('/user/<int:user_id>')
def user_posts(user_id):
user = User.query.get_or_404(user_id)
posts = Post.query.filter_by(user_id=user_id).order_by(Post.created_date.desc()).all()
return render_template('user_posts.html', user=user, posts=posts)
@app.route('/api/users/<int:user_id>', methods=['GET'])
def get_user_api(user_id):
user = User.query.get_or_404(user_id)
return jsonify({
'id': user.id,
'username': user.username,
'email': user.email,
'posts_count': len(user.posts)
})
REST API with Flask-RESTful
from flask import Flask
from flask_restful import Resource, Api, reqparse, fields, marshal_with
app = Flask(__name__)
api = Api(app)
# Database setup (using SQLAlchemy as above)
# Request parsers
user_parser = reqparse.RequestParser()
user_parser.add_argument('username', type=str, required=True, help='Username is required')
user_parser.add_argument('email', type=str, required=True, help='Email is required')
user_parser.add_argument('password', type=str, required=True, help='Password is required')
# Response marshaling
user_fields = {
'id': fields.Integer,
'username': fields.String,
'email': fields.String,
'uri': fields.Url('user')
}
class UserList(Resource):
@marshal_with(user_fields)
def get(self):
users = User.query.all()
return users
@marshal_with(user_fields)
def post(self):
args = user_parser.parse_args()
user = User(username=args['username'],
email=args['email'],
password_hash=generate_password_hash(args['password']))
db.session.add(user)
db.session.commit()
return user, 201
class User(Resource):
@marshal_with(user_fields)
def get(self, user_id):
user = User.query.get_or_404(user_id)
return user
@marshal_with(user_fields)
def put(self, user_id):
user = User.query.get_or_404(user_id)
args = user_parser.parse_args()
user.username = args['username']
user.email = args['email']
if args['password']:
user.password_hash = generate_password_hash(args['password'])
db.session.commit()
return user
def delete(self, user_id):
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
return '', 204
# Register routes
api.add_resource(UserList, '/api/users')
api.add_resource(User, '/api/users/<int:user_id>')
Django - Full-Featured Web Framework
Django Project Setup
# Install Django
pip install django djangorestframework
# Create project
django-admin startproject myproject
# Create app
cd myproject
python manage.py startapp blog
# Apply migrations
python manage.py makemigrations
python manage.py migrate
Django Models and Database
# blog/models.py
from django.db import models
from django.contrib.auth.models import User
from django.urls import reverse
class Author(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(max_length=500, blank=True)
website = models.URLField(blank=True)
def __str__(self):
return self.user.username
class Category(models.Model):
name = models.CharField(max_length=100, unique=True)
slug = models.SlugField(unique=True)
description = models.TextField(blank=True)
class Meta:
verbose_name_plural = "categories"
def __str__(self):
return self.name
class Post(models.Model):
title = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
content = models.TextField()
excerpt = models.TextField(blank=True)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
published = models.BooleanField(default=False)
def get_absolute_url(self):
return reverse('post_detail', kwargs={'slug': self.slug})
def __str__(self):
return self.title
Django Admin Interface
# blog/admin.py
from django.contrib import admin
from .models import Author, Category, Post
@admin.register(Author)
class AuthorAdmin(admin.ModelAdmin):
list_display = ['user', 'bio', 'website']
search_fields = ['user__username', 'bio']
@admin.register(Category)
class CategoryAdmin(admin.ModelAdmin):
list_display = ['name', 'slug', 'description']
prepopulated_fields = {'slug': ('name',)}
@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
list_display = ['title', 'author', 'category', 'created_at', 'published']
list_filter = ['published', 'category', 'created_at']
search_fields = ['title', 'content']
prepopulated_fields = {'slug': ('title',)}
Views and URL Configuration
# blog/views.py
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib.auth.decorators import login_required
from django.core.paginator import Paginator
from .models import Post, Category
def post_list(request):
posts = Post.objects.filter(published=True).order_by('-created_at')
# Pagination
paginator = Paginator(posts, 10) # 10 posts per page
page_number = request.GET.get('page')
page_obj = paginator.get_page(page_number)
return render(request, 'blog/post_list.html', {
'page_obj': page_obj,
'categories': Category.objects.all()
})
def post_detail(request, slug):
post = get_object_or_404(Post, slug=slug, published=True)
return render(request, 'blog/post_detail.html', {'post': post})
@login_required
def post_create(request):
if request.method == 'POST':
form = PostForm(request.POST)
if form.is_valid():
post = form.save(commit=False)
post.author = request.user.author
post.save()
return redirect('post_detail', slug=post.slug)
else:
form = PostForm()
return render(request, 'blog/post_form.html', {'form': form})
# blog/urls.py
from django.urls import path
from . import views
app_name = 'blog'
urlpatterns = [
path('', views.post_list, name='post_list'),
path('category/<slug:slug>/', views.category_posts, name='category_posts'),
path('<slug:slug>/', views.post_detail, name='post_detail'),
]
Django REST Framework
# Install: pip install djangorestframework
# blog/api/serializers.py
from rest_framework import serializers
from .models import Post, Category
class CategorySerializer(serializers.ModelSerializer):
class Meta:
model = Category
fields = ['id', 'name', 'slug', 'description']
class PostSerializer(serializers.ModelSerializer):
author = serializers.ReadOnlyField(source='author.user.username')
category_name = serializers.ReadOnlyField(source='category.name')
class Meta:
model = Post
fields = ['id', 'title', 'slug', 'content', 'excerpt',
'author', 'category', 'category_name',
'created_at', 'updated_at', 'published']
read_only_fields = ['created_at', 'updated_at']
def create(self, validated_data):
validated_data['author'] = self.context['request'].user.author
return super().create(validated_data)
# blog/api/views.py
from rest_framework import generics, permissions
from rest_framework.decorators import api_view, permission_classes
from rest_framework.response import Response
from rest_framework.reverse import reverse
from .models import Post, Category
from .serializers import PostSerializer, CategorySerializer
from .permissions import IsOwnerOrReadOnly
@api_view(['GET'])
def api_root(request, format=None):
return Response({
'posts': reverse('post-list', request=request, format=format),
'categories': reverse('category-list', request=request, format=format),
})
class PostList(generics.ListCreateAPIView):
queryset = Post.objects.filter(published=True)
serializer_class = PostSerializer
def perform_create(self, serializer):
serializer.save(author=self.request.user.author)
class PostDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Post.objects.all()
serializer_class = PostSerializer
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
class CategoryList(generics.ListAPIView):
queryset = Category.objects.all()
serializer_class = CategorySerializer
Django Templates
<!-- templates/blog/post_list.html -->
{% extends "base.html" %}
{% block content %}
<div class="blog-container">
<div class="categories">
<h3>Categories</h3>
<ul>
{% for category in categories %}
<li>
<a href="{% url 'blog:category_posts' category.slug %}">
{{ category.name }}
</a>
</li>
{% endfor %}
</ul>
</div>
<div class="posts">
<h1>Latest Posts</h1>
{% for post in page_obj %}
<article class="post">
<h2><a href="{{ post.get_absolute_url }}">{{ post.title }}</a></h2>
{% if post.excerpt %}
<p>{{ post.excerpt }}</p>
{% else %}
<p>{{ post.content|striptags|truncatewords:30 }}</p>
{% endif %}
<footer>
<span>By {{ post.author.user.get_full_name }}</span>
<span>in <a href="#">{{ post.category.name }}</a></span>
<time>{{ post.created_at|date:"M j, Y" }}</time>
</footer>
</article>
{% endfor %}
<!-- Pagination -->
<div class="pagination">
{% if page_obj.has_previous %}
<a href="?page={{ page_obj.previous_page_number }}">Previous</a>
{% endif %}
Page {{ page_obj.number }} of {{ page_obj.paginator.num_pages }}
{% if page_obj.has_next %}
<a href="?page={{ page_obj.next_page_number }}">Next</a>
{% endif %}
</div>
</div>
</div>
{% endblock %}
Testing and Deployment
Unit Tests in Flask
# tests/test_app.py
import pytest
from flask import Flask
from app import create_app, db
class TestApp:
def setup_method(self):
self.app = create_app('testing')
self.app_context = self.app.app_context()
self.app_context.push()
db.create_all()
def teardown_method(self):
db.session.remove()
db.drop_all()
self.app_context.pop()
def test_home_page(self):
client = self.app.test_client()
response = client.get('/')
assert response.status_code == 200
assert b'Welcome' in response.data
def test_user_api(self):
client = self.app.test_client()
response = client.get('/api/users')
assert response.status_code == 200
data = response.get_json()
assert 'users' in data
Django Tests
# blog/tests.py
from django.test import TestCase
from django.urls import reverse
from .models import Post, Author
class PostModelTest(TestCase):
def setUp(self):
self.author = Author.objects.create(
user=User.objects.create_user('testuser', '[email protected]', 'pass'),
bio='Test bio'
)
def test_post_creation(self):
post = Post.objects.create(
title='Test Post',
slug='test-post',
content='Test content',
author=self.author
)
self.assertEqual(post.title, 'Test Post')
class PostViewTest(TestCase):
def setUp(self):
# Create test data
self.author = Author.objects.create(
user=User.objects.create_user('author', '[email protected]', 'pass'),
bio='Test author'
)
self.post = Post.objects.create(
title='Published Post',
slug='published-post',
content='Published content',
author=self.author,
published=True
)
def test_post_list_view(self):
response = self.client.get(reverse('blog:post_list'))
self.assertEqual(response.status_code, 200)
self.assertContains(response, 'Published Post')
Deployment
Flask Deployment with Gunicorn
# Install Gunicorn
pip install gunicorn
# Run with Gunicorn
gunicorn -w 4 -b 0.0.0.0:8000 app:app
# For Django
pip install gunicorn
gunicorn myproject.wsgi:application --bind 0.0.0.0:8000
Docker Deployment
# Dockerfile for Flask
FROM python:3.9-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]
Best Practices
Security
- Use environment variables for secrets
- Validate all user inputs
- Use HTTPS for production
- Keep frameworks and dependencies updated
Performance
- Use database indexes appropriately
- Implement caching (Redis/Memcached)
- Use async/await for I/O operations (FastAPI for async Python)
- Optimize database queries (select_related, prefetch_related in Django)
Code Organization
# Flask app structure
myapp/
├── app.py
├── models.py
├── routes/
│ ├── auth.py
│ ├── blog.py
│ └── api.py
├── templates/
├── static/
├── tests/
└── config.py
# Django project structure
myproject/
├── myproject/
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── blog/
│ ├── models.py
│ ├── views.py
│ ├── urls.py
│ ├── admin.py
│ ├── apps.py
│ └── tests.py
├── api/
│ └── (DRF views and serializers)
├── static/
├── templates/
└── manage.py
Python provides powerful backend frameworks for web development. Flask offers flexibility and control for smaller applications, while Django provides a comprehensive framework for larger, complex projects. Both support modern web development practices and can be deployed efficiently in production environments.
This guide covers the fundamentals of Python backend development with Flask and Django. For advanced topics like Docker deployment, microservices, and cloud platforms, explore specialized documentation.
Updated: January 15, 2025
Author: Danial Pahlavan
Category: Web Development