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:
                    
                        - Create Post model (title, content, author, created_at, published)
 
                        - Run migrations
 
                        - Create views for: list, detail, create, update, delete
 
                        - Create templates for each view
 
                        - Implement URL patterns
 
                        - 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:
                    
                        - Add Comment model with ForeignKey to Post
 
                        - Add Tag model with ManyToMany to Post
 
                        - Create views to display comments under posts
 
                        - Add form to create new comments
 
                        - Implement tag filtering (show posts by tag)
 
                        - Configure Django Admin for all models
 
                        - 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!