作为Django框架最受欢迎的功能之一,Admin后台为开发者提供了开箱即用的数据管理界面。我在多个生产项目中深刻体会到,合理配置Admin可以节省大量CRUD界面开发时间。让我们从基础配置开始,逐步深入高级用法。
在全新Django项目中,Admin默认处于激活状态。但有几个关键配置需要检查:
python复制# settings.py 关键配置项
INSTALLED_APPS = [
'django.contrib.admin', # 必须存在
'django.contrib.auth', # 用户认证系统
'django.contrib.contenttypes', # 内容类型框架
# ...其他必要应用
]
MIDDLEWARE = [
'django.contrib.sessions.middleware.SessionMiddleware', # 会话支持
'django.contrib.auth.middleware.AuthenticationMiddleware', # 用户认证
# ...其他中间件
]
创建超级用户是访问Admin的第一步,这里有个实用技巧:
bash复制# 非交互式创建超级用户(适合自动化部署)
echo "from django.contrib.auth import get_user_model; User = get_user_model(); User.objects.create_superuser('admin', 'admin@example.com', 'password')" | python manage.py shell
模型注册看似简单,但有些细节需要注意:
python复制# blog/admin.py 基础注册
from django.contrib import admin
from .models import Article
# 最小化注册
admin.site.register(Article)
# 推荐注册方式(便于后续扩展)
@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
pass
提示:使用装饰器注册方式可以让模型类与Admin配置保持在同一个代码块,更易于维护。
实际项目中,我们需要对Admin界面进行全方位定制。以下是一个生产级配置示例:
python复制@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
# 列表页配置
list_display = ('id', 'title', 'author', 'category', 'status', 'pub_date')
list_editable = ('status',) # 可直接在列表页编辑的字段
list_filter = ('status', 'category', 'author')
search_fields = ('title', 'content', 'author__username')
list_per_page = 50
show_full_result_count = False # 大数据量时提升性能
# 编辑页配置
fieldsets = (
('基础信息', {
'fields': ('title', 'author', 'category')
}),
('内容', {
'fields': ('content', 'tags'),
'classes': ('wide',),
}),
('状态', {
'fields': ('status', 'pub_date'),
'classes': ('collapse',),
}),
)
filter_horizontal = ('tags',) # 多对多字段优化显示
readonly_fields = ('created_at',) # 只读字段
# 自定义方法字段
def view_count(self, obj):
return obj.views.count()
view_count.short_description = '访问量'
几个值得注意的高级技巧:
select_related和prefetch_related来减少查询次数:python复制def get_queryset(self, request):
return super().get_queryset(request)\
.select_related('author', 'category')\
.prefetch_related('tags')
python复制actions = ['make_published']
def make_published(self, request, queryset):
updated = queryset.update(status='published')
self.message_user(request, f"{updated}篇文章已发布")
make_published.short_description = "发布选中文章"
处理关联模型时,内联(Inline)管理非常实用。以下是评论功能的两种内联实现方式:
python复制# 表格形式内联(适合字段少的模型)
class CommentTabularInline(admin.TabularInline):
model = Comment
extra = 1
fields = ('name', 'email', 'content', 'is_approved')
readonly_fields = ('created_at',)
# 堆叠形式内联(适合字段多的模型)
class CommentStackedInline(admin.StackedInline):
model = Comment
extra = 0
fields = (('name', 'email'), 'content', 'is_approved')
classes = ('collapse',)
# 在ArticleAdmin中添加
inlines = [CommentTabularInline]
内联管理的实用技巧:
extra=0可以隐藏空表单classes = ('collapse',)可折叠内联区块max_num限制最大关联数量python复制def clean(self):
cleaned_data = super().clean()
if cleaned_data.get('expire_date') < timezone.now().date():
raise ValidationError("过期日期不能是过去时间")
return cleaned_data
python复制def get_queryset(self, request):
qs = super().get_queryset(request)
if not request.user.is_superuser:
qs = qs.filter(author=request.user)
return qs
通过重写以下方法可以自定义Admin模板:
change_view_templateadd_view_templatedelete_view_template例如创建templates/admin/blog/article/change_form.html来定制编辑页。
has_add_permission、has_change_permission等方法list_select_related和raw_id_fields优化查询readonly_fields,防止越权修改经验分享:在用户量大的系统中,建议禁用Admin的history功能(设置
save_as_continue=False和save_on_top=False)以减轻数据库压力。
创建记录的三种方式及其区别:
python复制# 方法1:create() - 最简洁
article = Article.objects.create(
title="ORM指南",
content="...",
status="draft"
)
# 方法2:save() - 需要显式调用
article = Article(title="ORM进阶")
article.save()
# 方法3:bulk_create - 批量创建(性能最佳)
articles = [
Article(title=f"文章{i}")
for i in range(100)
]
Article.objects.bulk_create(articles)
查询操作的关键方法:
python复制# 获取单个对象
try:
article = Article.objects.get(pk=1) # 主键查询
except Article.DoesNotExist:
handle_missing_article()
# 使用get_object_or_404简化视图代码
from django.shortcuts import get_object_or_404
article = get_object_or_404(Article, pk=1)
# 复杂查询
recent_articles = Article.objects.filter(
status="published",
pub_date__gte=timezone.now()-timedelta(days=7)
).exclude(
category__name="旧闻"
).order_by('-views')
更新操作的性能对比:
python复制# 低效方式(先查询再保存)
article = Article.objects.get(pk=1)
article.views += 1
article.save()
# 高效方式(直接更新)
Article.objects.filter(pk=1).update(views=F('views')+1)
# 批量更新
Article.objects.filter(
status="draft",
created_at__lt=timezone.now()-timedelta(days=30)
).update(status="expired")
删除操作的安全实践:
python复制# 安全删除 - 先确认存在
article = get_object_or_404(Article, pk=1)
article.delete()
# 批量删除
deleted, _ = Article.objects.filter(
status="spam"
).delete()
print(f"删除了{deleted}条记录")
Django ORM提供了丰富的字段查询条件:
python复制# 精确匹配
Article.objects.filter(title__exact="Django教程")
# 等价简写
Article.objects.filter(title="Django教程")
# 包含查询(区分大小写)
Article.objects.filter(content__contains="ORM")
# 不区分大小写查询
Article.objects.filter(title__icontains="django")
# 正则表达式查询
Article.objects.filter(title__regex=r"^Django.+教程$")
# 日期范围查询
today = timezone.now().date()
Article.objects.filter(
pub_date__date__range=(today-timedelta(days=7), today)
)
# JSON字段查询(Django 3.1+)
Article.objects.filter(
meta_data__author__name="John"
)
python复制# 此时不会查询数据库
queryset = Article.objects.filter(status="published")
# 这些操作会触发查询
list(queryset) # 转换为列表
queryset[0] # 获取单个对象
bool(queryset) # 判断是否存在
python复制queryset = Article.objects.all()
if keyword:
queryset = queryset.filter(title__icontains=keyword)
if category:
queryset = queryset.filter(category=category)
return queryset.order_by('-pub_date')
python复制# 第一次迭代 - 执行查询
articles = Article.objects.filter(status="published")
for article in articles:
print(article.title)
# 第二次迭代 - 使用缓存
for article in articles:
print(article.created_at)
python复制# 前10条记录
first_page = Article.objects.all()[:10]
# 第11-20条记录
second_page = Article.objects.all()[10:20]
# 注意:不支持负索引
定义模型关系时的注意事项:
python复制class Author(models.Model):
name = models.CharField(max_length=100)
# 便于反向查询的自定义related_name
articles = models.ManyToManyField(
'Article',
related_name='authors',
blank=True
)
class Article(models.Model):
title = models.CharField(max_length=200)
# 外键关系
category = models.ForeignKey(
'Category',
on_delete=models.SET_NULL,
null=True,
blank=True,
related_name='articles'
)
# 多对多关系
tags = models.ManyToManyField(
'Tag',
through='ArticleTag', # 使用中间模型
related_name='articles'
)
class ArticleTag(models.Model):
article = models.ForeignKey(Article, on_delete=models.CASCADE)
tag = models.ForeignKey(Tag, on_delete=models.CASCADE)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
unique_together = [['article', 'tag']]
关系字段的最佳实践:
related_name,避免默认的_set后缀through参数自定义中间模型on_delete行为:
CASCADE:级联删除PROTECT:阻止删除SET_NULL:设为NULL(需设置null=True)SET_DEFAULT:设为默认值python复制# 基本查询
articles = Article.objects.filter(category__name="技术")
# 多层跨表
articles = Article.objects.filter(
category__parent__name="编程"
)
# 使用select_related优化查询
articles = Article.objects.select_related(
'category', 'author'
).all()
python复制# 基本反向查询
categories = Category.objects.filter(
articles__title__contains="Django"
).distinct()
# 使用prefetch_related优化多对多
categories = Category.objects.prefetch_related(
'articles'
).all()
for category in categories:
print(category.articles.count()) # 不会产生N+1查询
python复制# 查询包含特定标签的文章
articles = Article.objects.filter(
tags__name__in=["Python", "Django"]
).distinct()
# 查询某篇文章的所有标签
article = Article.objects.get(pk=1)
tags = article.tags.all()
# 通过中间模型查询
article_tags = ArticleTag.objects.filter(
article__category__name="技术",
tag__name__startswith="Py"
).select_related('tag')
python复制# 优化前:N+1查询问题
articles = Article.objects.all()
for article in articles:
print(article.category.name) # 每次循环都查询数据库
# 优化后:1次查询
articles = Article.objects.select_related('category').all()
for article in articles:
print(article.category.name) # 无额外查询
python复制# 优化前:N+1查询问题
categories = Category.objects.all()
for category in categories:
print(category.articles.count()) # 每次循环都查询数据库
# 优化后:2次查询
categories = Category.objects.prefetch_related('articles').all()
for category in categories:
print(category.articles.count()) # 无额外查询
python复制# 只获取必要字段
articles = Article.objects.only('title', 'pub_date')
# 排除大字段
articles = Article.objects.defer('content')
python复制# 批量创建
Article.objects.bulk_create([
Article(title=f"文章{i}") for i in range(1000)
])
# 批量更新
Article.objects.filter(
status="draft"
).update(status="published")
Django提供了多种聚合函数:
python复制from django.db.models import (
Count, Sum, Avg,
Max, Min, StdDev, Variance
)
# 基本聚合
stats = Article.objects.aggregate(
total=Count('id'),
avg_views=Avg('views'),
max_date=Max('pub_date')
)
# 分组聚合
from django.db.models import F
category_stats = Category.objects.annotate(
article_count=Count('articles'),
total_views=Sum('articles__views'),
avg_rating=Avg('articles__rating')
).filter(
article_count__gt=0
).order_by('-total_views')
F对象不仅可以用于简单计算,还能实现复杂业务逻辑:
python复制# 原子更新
Article.objects.filter(pk=1).update(
views=F('views') + 1
)
# 条件表达式
from django.db.models import Case, When, Value
Article.objects.annotate(
popularity=Case(
When(views__gt=1000, then=Value('热门')),
When(views__gt=100, then=Value('一般')),
default=Value('冷门')
)
)
# 多字段计算
Article.objects.annotate(
score=F('views') * 0.6 + F('likes') * 0.4
).order_by('-score')
Q对象可以实现复杂的逻辑组合:
python复制from django.db.models import Q
# 基本用法
Article.objects.filter(
Q(title__contains='Django') | Q(content__contains='Django')
)
# 动态构建查询
def build_search_query(params):
query = Q()
if params.get('keyword'):
query &= Q(
Q(title__icontains=params['keyword']) |
Q(content__icontains=params['keyword'])
)
if params.get('category'):
query &= Q(category__id=params['category'])
if params.get('start_date'):
query &= Q(pub_date__gte=params['start_date'])
return query
# 在视图中使用
query = build_search_query(request.GET)
articles = Article.objects.filter(query)
对于特殊需求,可以创建自定义数据库函数:
python复制from django.db.models import Func
class LevenshteinDistance(Func):
function = 'LEVENSHTEIN'
arity = 2
# 使用示例
Article.objects.annotate(
distance=LevenshteinDistance('title', 'Django教程')
).order_by('distance')
问题1:使用count()判断存在性
python复制# 低效
if Article.objects.filter(author=user).count() > 0:
pass
# 高效
if Article.objects.filter(author=user).exists():
pass
问题2:不必要的排序
python复制# 低效(排序消耗资源)
articles = list(Article.objects.order_by('title'))
# 更高效(如不需要排序)
articles = list(Article.objects.all())
问题3:大表的分页性能
python复制# 低效的OFFSET分页
Article.objects.all()[10000:10010]
# 高效的关键字分页
last_id = get_last_id_from_request()
Article.objects.filter(id__gt=last_id)[:10]
python复制LOGGING = {
'version': 1,
'handlers': {
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
},
},
'loggers': {
'django.db.backends': {
'level': 'DEBUG',
'handlers': ['console'],
},
},
}
python复制print(Article.objects.filter(title__contains='Django').explain())
python复制DATABASES = {
'default': {
'ENGINE': 'django_db_geventpool.backends.postgresql_psycopg2',
'CONN_MAX_AGE': 0,
'OPTIONS': {
'MAX_CONNS': 20,
'REUSE_CONNS': 10
}
}
}
在实际项目中,我发现ORM的性能问题90%以上源于不当的使用方式而非ORM本身。掌握这些高级技巧后,Django ORM完全可以胜任高并发场景下的数据操作需求。