第 8 章 · 静态资源、媒体上传与模板进阶
本章目标:正确配置 STATIC_URL / STATICFILES_DIRS / STATIC_ROOT 与 MEDIA;在模型中使用 ImageField 实现商品封面;模板中加载 {% static %};掌握 collectstatic 生产部署流程;运用模板继承与 include 构建 shop-demo 后台页面骨架。
学时建议:4 小时(含 1.5 小时图片上传跟练)
前置:本模块 ch01 模板基础;ch02 表单与 request.FILES;ch05 Product 模型;ch07 API(本章侧重服务端渲染与静态资源)。
8.1 静态文件 vs 媒体文件
| 类型 | 来源 | 示例 | 部署 |
|---|---|---|---|
| 静态 Static | 开发者提交到仓库 | CSS、JS、Logo、favicon | collectstatic 收集到 STATIC_ROOT |
| 媒体 Media | 用户上传 | 商品封面、用户头像 | 存 MEDIA_ROOT,由 Web 服务器或对象存储提供 |
开发环境 runserver
/static/ → django.contrib.staticfiles
/media/ → settings 中 DEBUG 时映射 MEDIA_ROOT
生产环境 Nginx
/static/ → alias /var/www/shop-demo/static/
/media/ → alias /var/www/shop-demo/media/ (或 OSS CDN)
本章基于虚构电商 shop-demo,域名示例 https://api.example.com 仅用于说明 CDN 地址,不涉及真实业务配置。
8.2 settings.py 完整配置
shopdemo/settings.py:
from pathlib import Path
BASE_DIR = Path(__file__).resolve().parent.parent
# --- Static ---
STATIC_URL = "/static/"
STATICFILES_DIRS = [
BASE_DIR / "assets", # 源码中的静态目录
]
STATIC_ROOT = BASE_DIR / "staticfiles" # collectstatic 输出目录
# --- Media ---
MEDIA_URL = "/media/"
MEDIA_ROOT = BASE_DIR / "media"
# 开发期确保 staticfiles 应用已启用
INSTALLED_APPS = [
# ...
"django.contrib.staticfiles",
]
| 变量 | 含义 |
|---|---|
STATIC_URL | 浏览器访问前缀 |
STATICFILES_DIRS | 开发时额外静态目录(可多个) |
STATIC_ROOT | 生产收集目标(勿把源码直接放这里) |
MEDIA_ROOT | 上传文件磁盘路径 |
目录结构建议:
shop-demo/
├── assets/
│ ├── css/
│ │ └── admin-base.css
│ ├── js/
│ │ └── product-preview.js
│ └── img/
│ └── logo.png
├── media/ # .gitignore,不上传用户文件
│ └── products/
├── staticfiles/ # collectstatic 产出,.gitignore
└── templates/
├── base.html
└── catalog/
└── product_form.html
8.3 开发环境 URL 映射
shopdemo/urls.py:
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path("admin/", admin.site.urls),
path("api/", include("catalog.urls")),
path("", include("catalog.web_urls")), # 服务端页面路由
]
if settings.DEBUG:
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
# DEBUG 时 staticfiles 自动处理 STATIC_URL,一般无需再 manual static()
注意:DEBUG=False 时 Django 不再托管媒体文件,必须由 Nginx 或云存储处理。
8.4 ImageField 与 Pillow
pip install Pillow
catalog/models.py:
from django.db import models
def product_cover_path(instance, filename):
ext = filename.rsplit(".", 1)[-1].lower()
return f"products/{instance.slug}.{ext}"
class Product(models.Model):
category = models.ForeignKey(
"catalog.Category",
on_delete=models.PROTECT,
related_name="products",
)
name = models.CharField(max_length=200)
slug = models.SlugField(unique=True)
price = models.DecimalField(max_digits=10, decimal_places=2)
stock = models.PositiveIntegerField(default=0)
cover = models.ImageField(upload_to=product_cover_path, blank=True, null=True)
description = models.TextField(blank=True)
is_published = models.BooleanField(default=False)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
def __str__(self):
return self.name
category沿用 ch05 的分类外键(PROTECT防误删有商品的分类);本章在此基础上新增cover/description/updated_at字段。
迁移:
python manage.py makemigrations catalog
python manage.py migrate
表单处理(ch02 ModelForm 延伸):
# catalog/forms.py
from django import forms
from .models import Product
class ProductForm(forms.ModelForm):
class Meta:
model = Product
fields = ["name", "slug", "price", "stock", "cover", "description", "is_published"]
widgets = {
"description": forms.Textarea(attrs={"rows": 4}),
}