Django文件上传
一、今日学习内容概述
学习模块 | 重要程度 | 主要内容 |
---|---|---|
基础文件上传 | ⭐⭐⭐⭐⭐ | 文件字段、基本配置 |
自定义存储 | ⭐⭐⭐⭐⭐ | 存储后端、云存储集成 |
文件处理 | ⭐⭐⭐⭐ | 图片处理、文件验证 |
异步上传 | ⭐⭐⭐⭐ | AJAX上传、进度显示 |
二、模型和表单设计
# models.py
from django.db import models
from django.core.validators import FileExtensionValidator
import uuid
import osdef get_file_path(instance, filename):"""生成唯一的文件路径"""ext = filename.split('.')[-1]filename = f'{uuid.uuid4()}.{ext}'return os.path.join('uploads', filename)class Document(models.Model):title = models.CharField('标题', max_length=200)file = models.FileField('文件',upload_to=get_file_path,validators=[FileExtensionValidator(['pdf', 'doc', 'docx'])])uploaded_at = models.DateTimeField('上传时间', auto_now_add=True)class Meta:verbose_name = '文档'verbose_name_plural = verbose_namedef __str__(self):return self.titleclass Image(models.Model):title = models.CharField('标题', max_length=200)image = models.ImageField('图片',upload_to='images/%Y/%m/%d/',validators=[FileExtensionValidator(['jpg', 'jpeg', 'png'])])thumbnail = models.ImageField('缩略图',upload_to='thumbnails/%Y/%m/%d/',null=True,blank=True)uploaded_at = models.DateTimeField('上传时间', auto_now_add=True)class Meta:verbose_name = '图片'verbose_name_plural = verbose_name
三、自定义存储后端
# storage.py
from django.core.files.storage import Storage
from django.conf import settings
import oss2
import osclass AliyunOSSStorage(Storage):"""阿里云OSS存储后端"""def __init__(self):self.access_key_id = settings.OSS_ACCESS_KEY_IDself.access_key_secret = settings.OSS_ACCESS_KEY_SECRETself.bucket_name = settings.OSS_BUCKET_NAMEself.endpoint = settings.OSS_ENDPOINT# 初始化OSS客户端auth = oss2.Auth(self.access_key_id, self.access_key_secret)self.bucket = oss2.Bucket(auth, self.endpoint, self.bucket_name)def _save(self, name, content):"""保存文件到OSS"""self.bucket.put_object(name, content)return namedef _open(self, name, mode='rb'):"""从OSS读取文件"""return self.bucket.get_object(name)def exists(self, name):"""检查文件是否存在"""try:self.bucket.get_object_meta(name)return Trueexcept:return Falsedef url(self, name):"""获取文件URL"""return f'https://{self.bucket_name}.{self.endpoint}/{name}'# settings.py 配置
DEFAULT_FILE_STORAGE = 'myapp.storage.AliyunOSSStorage'
四、文件处理视图
# views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from django.core.files.storage import default_storage
from django.views.decorators.csrf import csrf_exempt
from .forms import DocumentForm, ImageForm
from PIL import Image as PILImage
from io import BytesIO
import jsondef handle_uploaded_file(request):if request.method == 'POST':form = DocumentForm(request.POST, request.FILES)if form.is_valid():document = form.save()messages.success(request, '文件上传成功!')return redirect('document_list')else:form = DocumentForm()return render(request, 'upload/document_form.html', {'form': form})@csrf_exempt
def ajax_upload(request):if request.method == 'POST':try:file = request.FILES['file']# 保存文件filename = default_storage.save(file.name, file)url = default_storage.url(filename)return JsonResponse({'success': True,'url': url})except Exception as e:return JsonResponse({'success': False,'error': str(e)})return JsonResponse({'success': False})def handle_image_upload(request):if request.method == 'POST':form = ImageForm(request.POST, request.FILES)if form.is_valid():image = form.save(commit=False)# 创建缩略图if image.image:img = PILImage.open(image.image)thumb_size = (300, 300)img.thumbnail(thumb_size)# 保存缩略图thumb_io = BytesIO()img.save(thumb_io, format=img.format)thumb_filename = f'thumb_{image.image.name}'image.thumbnail.save(thumb_filename,thumb_io.getvalue(),save=False)image.save()messages.success(request, '图片上传成功!')return redirect('image_list')else:form = ImageForm()return render(request, 'upload/image_form.html', {'form': form})
五、文件上传流程图
六、模板实现
<!-- templates/upload/image_form.html -->
{% extends "base.html" %}{% block content %}
<div class="container mt-4"><h2>上传图片</h2><form method="post" enctype="multipart/form-data">{% csrf_token %}{% if form.errors %}<div class="alert alert-danger">{% for field in form %}{% for error in field.errors %}<p>{{ error }}</p>{% endfor %}{% endfor %}</div>{% endif %}<div class="form-group">{{ form.title.label_tag }}{{ form.title }}</div><div class="form-group">{{ form.image.label_tag }}{{ form.image }}<small class="form-text text-muted">支持的格式:JPG, JPEG, PNG</small></div><div id="preview" class="mt-3 mb-3"></div><button type="submit" class="btn btn-primary">上传</button></form>
</div><script>
document.querySelector('input[type="file"]').addEventListener('change', function(e) {const file = e.target.files[0];if (file) {const reader = new FileReader();reader.onload = function(e) {const preview = document.getElementById('preview');preview.innerHTML = `<img src="${e.target.result}" class="img-thumbnail" style="max-width: 300px;">`;}reader.readAsDataURL(file);}
});
</script>
{% endblock %}
七、文件验证器
# validators.py
from django.core.exceptions import ValidationError
import magic
import osdef validate_file_type(file):"""验证文件类型"""mime = magic.from_buffer(file.read(1024), mime=True)file.seek(0) # 重置文件指针allowed_types = {'application/pdf': 'pdf','application/msword': 'doc','application/vnd.openxmlformats-officedocument.wordprocessingml.document': 'docx'}if mime not in allowed_types:raise ValidationError('不支持的文件类型')def validate_file_size(file):"""验证文件大小"""max_size = 5 * 1024 * 1024 # 5MBif file.size > max_size:raise ValidationError('文件大小不能超过5MB')
八、异步上传实现
// static/js/upload.js
class FileUploader {constructor(options) {this.options = {url: '/upload/',maxSize: 5 * 1024 * 1024,allowedTypes: ['image/jpeg', 'image/png'],...options};this.init();}init() {this.fileInput = document.querySelector(this.options.fileInput);this.progressBar = document.querySelector(this.options.progressBar);this.fileInput.addEventListener('change', (e) => this.handleFiles(e));}handleFiles(e) {const files = e.target.files;Array.from(files).forEach(file => this.uploadFile(file));}uploadFile(file) {if (!this.validateFile(file)) return;const formData = new FormData();formData.append('file', file);const xhr = new XMLHttpRequest();xhr.open('POST', this.options.url, true);xhr.upload.addEventListener('progress', (e) => {if (e.lengthComputable) {const percent = (e.loaded / e.total) * 100;this.updateProgress(percent);}});xhr.onload = () => {if (xhr.status === 200) {this.options.onSuccess && this.options.onSuccess(xhr.response);} else {this.options.onError && this.options.onError(xhr.statusText);}};xhr.send(formData);}validateFile(file) {if (file.size > this.options.maxSize) {alert('文件太大');return false;}if (!this.options.allowedTypes.includes(file.type)) {alert('不支持的文件类型');return false;}return true;}updateProgress(percent) {this.progressBar.style.width = `${percent}%`;this.progressBar.textContent = `${Math.round(percent)}%`;}
}// 使用示例
const uploader = new FileUploader({fileInput: '#fileInput',progressBar: '#progressBar',onSuccess: (response) => {console.log('上传成功', response);},onError: (error) => {console.error('上传失败', error);}
});
通过本章学习,你应该能够:
- 实现基本的文件上传功能
- 自定义存储后端
- 处理文件验证和安全
- 实现异步文件上传
怎么样今天的内容还满意吗?再次感谢朋友们的观看,关注GZH:凡人的AI工具箱,回复666,送您价值199的AI大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!