Django框架学习第2天:Django模型

一、课程概述

每天40分玩转Django:Django模型-LMLPHP

二、模型定义

2.1 基本模型结构

# blog/models.py
from django.db import models
from django.utils import timezone
from django.contrib.auth.models import User

class Category(models.Model):
    name = models.CharField(max_length=100, verbose_name='分类名称')
    description = models.TextField(blank=True, verbose_name='分类描述')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')

    class Meta:
        verbose_name = '分类'
        verbose_name_plural = verbose_name
        ordering = ['-created_time']

    def __str__(self):
        return self.name

class Post(models.Model):
    STATUS_CHOICES = (
        ('draft', '草稿'),
        ('published', '发布'),
    )
    
    title = models.CharField(max_length=200, verbose_name='标题')
    slug = models.SlugField(max_length=250, unique_for_date='publish')
    author = models.ForeignKey(User, on_delete=models.CASCADE, related_name='blog_posts')
    body = models.TextField(verbose_name='正文')
    publish = models.DateTimeField(default=timezone.now, verbose_name='发布时间')
    created = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    updated = models.DateTimeField(auto_now=True, verbose_name='更新时间')
    status = models.CharField(max_length=10, choices=STATUS_CHOICES, default='draft')
    category = models.ForeignKey(Category, on_delete=models.CASCADE, verbose_name='分类')
    tags = models.ManyToManyField('Tag', blank=True, verbose_name='标签')

    class Meta:
        ordering = ('-publish',)
        verbose_name = '文章'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.title

class Tag(models.Model):
    name = models.CharField(max_length=100, verbose_name='标签名称')
    created_time = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')

    class Meta:
        verbose_name = '标签'
        verbose_name_plural = verbose_name

    def __str__(self):
        return self.name

2.2 常用字段类型

2.3 字段选项

# 字段选项示例
class Product(models.Model):
    name = models.CharField(
        max_length=100,           # 最大长度
        verbose_name='商品名称',   # 人性化名称
        unique=True,              # 唯一值
        db_index=True,           # 数据库索引
        help_text='请输入商品名称' # 帮助文本
    )
    price = models.DecimalField(
        max_digits=10,           # 最大位数
        decimal_places=2,        # 小数位数
        null=True,               # 允许为空
        blank=True              # 表单可以为空
    )

三、ORM操作

3.1 基本的CRUD操作

# 创建对象
# 方法1:
post = Post(title='第一篇博客', body='内容...', author=user)
post.save()

# 方法2:
Post.objects.create(title='第二篇博客', body='内容...', author=user)

# 查询对象
# 获取所有文章
posts = Post.objects.all()

# 获取单个对象
post = Post.objects.get(id=1)

# 过滤对象
draft_posts = Post.objects.filter(status='draft')
recent_posts = Post.objects.filter(publish__year=2024)

# 更新对象
# 方法1:
post = Post.objects.get(id=1)
post.title = '新标题'
post.save()

# 方法2:
Post.objects.filter(id=1).update(title='新标题')

# 删除对象
post = Post.objects.get(id=1)
post.delete()

3.2 高级查询

# 查询API示例
from django.db.models import Q, Count, Avg
from django.utils import timezone

# Q对象 - 复杂查询
# 查找标题包含"Django"或内容包含"Python"的文章
Post.objects.filter(Q(title__contains='Django') | Q(body__contains='Python'))

# 查找不是草稿且作者是指定用户的文章
Post.objects.filter(~Q(status='draft'), author=user)

# 聚合查询
# 统计每个分类下的文章数量
categories = Category.objects.annotate(post_count=Count('post'))

# 查找文章数量大于5的分类
popular_categories = Category.objects.annotate(
    post_count=Count('post')
).filter(post_count__gt=5)

# 关联查询
# 获取指定作者的所有文章
user.blog_posts.all()

# 获取包含指定标签的所有文章
tag = Tag.objects.get(name='Django')
tag.post_set.all()

3.3 查询优化

# select_related() - 处理外键和一对一关系
# 一次性获取文章及其作者和分类信息
posts = Post.objects.select_related('author', 'category').all()

# prefetch_related() - 处理多对多关系
# 一次性获取文章及其标签信息
posts = Post.objects.prefetch_related('tags').all()

# 组合使用
posts = Post.objects.select_related('author', 'category').prefetch_related('tags').all()

四、数据库迁移

4.1 迁移命令

# 生成迁移文件
python manage.py makemigrations

# 执行迁移
python manage.py migrate

# 查看迁移文件的SQL语句
python manage.py sqlmigrate blog 0001

# 检查迁移文件
python manage.py check

4.2 数据迁移示例

# migrations/0002_auto_20240112_1234.py
from django.db import migrations, models

def populate_slug(apps, schema_editor):
    Post = apps.get_model('blog', 'Post')
    for post in Post.objects.all():
        post.slug = f"{post.title.lower().replace(' ', '-')}-{post.id}"
        post.save()

class Migration(migrations.Migration):

    dependencies = [
        ('blog', '0001_initial'),
    ]

    operations = [
        migrations.AddField(
            model_name='post',
            name='slug',
            field=models.SlugField(default='', max_length=250),
            preserve_default=False,
        ),
        migrations.RunPython(populate_slug),
    ]

五、实战练习:博客评论系统

# blog/models.py
class Comment(models.Model):
    post = models.ForeignKey(Post, on_delete=models.CASCADE, related_name='comments')
    name = models.CharField(max_length=80, verbose_name='评论者')
    email = models.EmailField(verbose_name='邮箱')
    body = models.TextField(verbose_name='评论内容')
    created = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    active = models.BooleanField(default=True, verbose_name='是否显示')

    class Meta:
        ordering = ('created',)
        verbose_name = '评论'
        verbose_name_plural = verbose_name

    def __str__(self):
        return f'Comment by {self.name} on {self.post}'

5.1 评论视图

# blog/views.py
from django.shortcuts import render, get_object_or_404
from .models import Post, Comment
from .forms import CommentForm

def post_detail(request, year, month, day, post):
    post = get_object_or_404(Post,
                            slug=post,
                            status='published',
                            publish__year=year,
                            publish__month=month,
                            publish__day=day)
    
    # 获取该文章的所有活动评论
    comments = post.comments.filter(active=True)
    
    if request.method == 'POST':
        comment_form = CommentForm(data=request.POST)
        if comment_form.is_valid():
            # 创建评论对象但不保存到数据库
            new_comment = comment_form.save(commit=False)
            # 指定评论的文章
            new_comment.post = post
            # 保存评论到数据库
            new_comment.save()
    else:
        comment_form = CommentForm()
        
    return render(request,
                 'blog/post/detail.html',
                 {'post': post,
                  'comments': comments,
                  'comment_form': comment_form})

六、进阶技巧

6.1 自定义模型管理器

# blog/models.py
class PublishedManager(models.Manager):
    def get_queryset(self):
        return super().get_queryset().filter(status='published')

class Post(models.Model):
    # ... 其他字段 ...
    objects = models.Manager()  # 默认管理器
    published = PublishedManager()  # 自定义管理器

# 使用自定义管理器
published_posts = Post.published.all()

6.2 模型信号

# blog/signals.py
from django.db.models.signals import post_save
from django.dispatch import receiver
from .models import Post

@receiver(post_save, sender=Post)
def create_post_slug(sender, instance, created, **kwargs):
    if created:
        instance.slug = f"{instance.title.lower().replace(' ', '-')}-{instance.id}"
        instance.save()

七、常见问题和解决方案

  1. 数据库迁移冲突:
# 重置迁移
python manage.py migrate blog zero
python manage.py makemigrations blog
python manage.py migrate blog
  1. 关联字段删除:
# 使用on_delete选项
author = models.ForeignKey(
    User,
    on_delete=models.SET_NULL,  # 设置为NULL
    null=True,
    blank=True
)
  1. 大数据查询优化:
# 分批处理
from django.core.paginator import Paginator
paginator = Paginator(Post.objects.all(), 100)
for page in paginator.page_range:
    for post in paginator.page(page).object_list:
        # 处理每篇文章
        pass

八、作业和练习

  1. 创建一个完整的博客数据模型,包含用户、分类、标签、文章和评论
  2. 实现文章的增删改查功能
  3. 添加评论功能
  4. 实现文章分类和标签功能
  5. 优化查询性能

九、扩展阅读

  1. Django Model字段类型详解
  2. 数据库事务处理
  3. Django ORM高级查询技巧
  4. 数据库性能优化方案

怎么样今天的内容还满意吗?再次感谢朋友们的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!

12-13 06:17