← Back to Module

Django ORM & CRUD Operations

CMM721: Web Application Development - Session 7

Birmingham Newman University

Lecturer: James Williams

Masters Level Conversion Course

3-hour session • 18 slides • 2 tasks • Database visualizations

Session Timeline:

  • 10 min: Registration & waiting
  • 20 min: Opening slides
  • 45 min: Task 1
  • 15 min: Break/Catch up
  • 20 min: Secondary slides
  • 45 min: Task 2
  • Remaining: Self-study

Learning Objectives

  • Create and manage Django models
  • Understand database migrations
  • Perform CRUD operations using Django ORM
  • Work with QuerySets and filtering
  • Implement model relationships
  • Use Django Admin interface

What is an ORM?

ORM: Object-Relational Mapping
  • Purpose: Interact with databases using Python objects
  • Benefit: Write Python instead of SQL
  • Database Agnostic: Works with SQLite, PostgreSQL, MySQL, etc.

Without ORM (Raw SQL):

cursor.execute("SELECT * FROM users WHERE age > 21")

With ORM:

User.objects.filter(age__gt=21)

Creating Django Models

# models.py
from django.db import models
from django.contrib.auth.models import User

class Post(models.Model):
  title = models.CharField(max_length=200)
  content = models.TextField()
  author = models.ForeignKey(User, on_delete=models.CASCADE)
  created_at = models.DateTimeField(auto_now_add=True)
  updated_at = models.DateTimeField(auto_now=True)
  published = models.BooleanField(default=False)

  def __str__(self):
    return self.title

  class Meta:
    ordering = ['-created_at']

Field Types

# Character/Text fields
CharField(max_length=100)
TextField()
EmailField()
URLField()

# Numeric fields
IntegerField()
DecimalField(max_digits=10, decimal_places=2)
FloatField()

# Date/Time fields
DateField()
DateTimeField()
TimeField()

# Boolean
BooleanField(default=False)

# File fields
FileField(upload_to='files/')
ImageField(upload_to='images/')

🎯 Database Table Visualization

Post Model → Database Table

id title content author_id created_at published
1 Django Tutorial Learn Django ORM... 1 2024-01-15 10:30
2 Python Basics Introduction to Python... 2 2024-01-16 14:20
3 Web Security Best practices... 1 2024-01-17 09:15

Django automatically creates an 'id' primary key field

Migrations

Create Migrations:

# Create migration files
python manage.py makemigrations

# Output:
# Migrations for 'blog':
#   blog/migrations/0001_initial.py
#     - Create model Post

# Apply migrations
python manage.py migrate

# View SQL
python manage.py sqlmigrate blog 0001

# Show migration status
python manage.py showmigrations
Always: makemigrations → migrate

CRUD: Create

Creating Objects:

# Method 1: Create and save
post = Post()
post.title = "My First Post"
post.content = "Hello World!"
post.author = request.user
post.save()

# Method 2: Create in one line
post = Post.objects.create(
  title="My First Post",
  content="Hello World!",
  author=request.user
)

# Method 3: get_or_create
post, created = Post.objects.get_or_create(
  title="Unique Title",
  defaults={'content': 'Content', 'author': user}
)

CRUD: Read (Query)

# Get all objects
posts = Post.objects.all()

# Get specific object
post = Post.objects.get(id=1)
post = Post.objects.get(title="Django Tutorial")

# Filter objects
published = Post.objects.filter(published=True)
recent = Post.objects.filter(created_at__gte='2024-01-01')

# Exclude
drafts = Post.objects.exclude(published=True)

# First/Last
first_post = Post.objects.first()
latest = Post.objects.last()

# Count
count = Post.objects.count()

Advanced Queries

# Chaining filters
posts = Post.objects.filter(
  published=True
).filter(
  created_at__year=2024
).order_by('-created_at')

# OR queries
from django.db.models import Q
posts = Post.objects.filter(
  Q(title__icontains='django') | Q(content__icontains='django')
)

# Lookup types
Post.objects.filter(title__icontains='python') # Case-insensitive
Post.objects.filter(title__startswith='Django')
Post.objects.filter(created_at__year=2024)
Post.objects.filter(id__in=[1, 2, 3])
Post.objects.filter(title__isnull=False)

CRUD: Update

# Method 1: Get, modify, save
post = Post.objects.get(id=1)
post.title = "Updated Title"
post.published = True
post.save()

# Method 2: Update queryset (bulk update)
Post.objects.filter(author=user).update(published=True)

# Update specific fields only
post = Post.objects.get(id=1)
post.title = "New Title"
post.save(update_fields=['title']) # Only update title
Note: .update() doesn't call save() or send signals

CRUD: Delete

# Delete single object
post = Post.objects.get(id=1)
post.delete()

# Delete multiple objects
Post.objects.filter(published=False).delete()

# Delete all (BE CAREFUL!)
Post.objects.all().delete()
Warning: Delete operations are irreversible!

Task 1: Build Blog CRUD

Objective:

Create a fully functional blog with CRUD operations

Requirements:

  1. Create Post model (title, content, author, created_at, published)
  2. Run migrations
  3. Create views for: list, detail, create, update, delete
  4. Create templates for each view
  5. Implement URL patterns
  6. Test all CRUD operations

Views to Implement:

  • post_list - Display all posts
  • post_detail - Show single post
  • post_create - Form to create post
  • post_update - Form to edit post
  • post_delete - Confirm and delete

Model Relationships

ForeignKey (Many-to-One):

class Comment(models.Model):
  post = models.ForeignKey(Post, on_delete=models.CASCADE)
  text = models.TextField()
  author = models.ForeignKey(User, on_delete=models.CASCADE)

# Access
post.comment_set.all() # All comments for a post
comment.post # The post this comment belongs to

on_delete Options:

  • CASCADE: Delete related objects
  • PROTECT: Prevent deletion
  • SET_NULL: Set to NULL
  • SET_DEFAULT: Set to default value

🎯 Relationship Visualization

Blog Database Schema

User

• id (PK)
• username
• email

Post

• id (PK)
• title
• content
• author_id (FK)
• created_at

Comment

• id (PK)
• post_id (FK)
• author_id (FK)
• text
• created_at

PK = Primary Key | FK = Foreign Key

Many-to-Many Relationships

class Tag(models.Model):
  name = models.CharField(max_length=50)

class Post(models.Model):
  title = models.CharField(max_length=200)
  tags = models.ManyToManyField(Tag)

# Usage
post = Post.objects.get(id=1)
tag = Tag.objects.get(name='Django')

# Add tag to post
post.tags.add(tag)

# Get all tags for a post
post.tags.all()

# Get all posts with a tag
Tag.objects.get(name='Python').post_set.all()

# Remove tag
post.tags.remove(tag)

Django Admin

Register Models (admin.py):

from django.contrib import admin
from .models import Post, Comment

@admin.register(Post)
class PostAdmin(admin.ModelAdmin):
  list_display = ('title', 'author', 'created_at', 'published')
  list_filter = ('published', 'created_at')
  search_fields = ('title', 'content')
  date_hierarchy = 'created_at'
  ordering = ('-created_at',)

admin.site.register(Comment)

Create Superuser:

python manage.py createsuperuser
# Visit: http://127.0.0.1:8000/admin/

Task 2: Advanced Blog Features

Objective:

Enhance blog with relationships and admin interface

Requirements:

  1. Add Comment model with ForeignKey to Post
  2. Add Tag model with ManyToMany to Post
  3. Create views to display comments under posts
  4. Add form to create new comments
  5. Implement tag filtering (show posts by tag)
  6. Configure Django Admin for all models
  7. Customize admin: list_display, search, filters

Bonus:

  • Add pagination (10 posts per page)
  • Implement comment count per post
  • Add "related posts" by shared tags

QuerySet Optimization

select_related (ForeignKey):

# Without optimization (N+1 queries)
posts = Post.objects.all()
for post in posts:
  print(post.author.username) # Database hit each time

# With select_related (1 query with JOIN)
posts = Post.objects.select_related('author').all()
for post in posts:
  print(post.author.username) # No extra queries

prefetch_related (ManyToMany):

# Optimize ManyToMany
posts = Post.objects.prefetch_related('tags').all()
for post in posts:
  print(post.tags.all()) # Already loaded

Best Practices

  • Always use __str__: Define readable string representation
  • Indexes: Add db_index=True for frequently queried fields
  • Validation: Use clean() method for custom validation
  • Migrations: Review migration files before applying
  • QuerySet optimization: Use select_related/prefetch_related
  • Bulk operations: Use bulk_create() for multiple inserts
  • Admin: Customize admin interface for better usability
Remember: QuerySets are lazy - they don't hit database until evaluated

Next Session: Django REST Framework

  • Introduction to REST APIs
  • Django REST Framework (DRF) basics
  • Serializers and validation
  • ViewSets and Routers
  • Authentication and permissions
  • API testing with Postman

Preparation: Install: pip install djangorestframework

Questions & Discussion

Contact: JWilliams@Staff.newman.ac.uk

Office Hours: By appointment

Resources: Django ORM Docs, QuerySet API Reference

Thank you for your attention!