每天40分玩转Django:Django文件上传
Django文件上传
一、今日学习内容概述
学习模块 | 重要程度 | 主要内容 |
---|---|---|
基础文件上传 | ⭐⭐⭐⭐⭐ | 文件字段、基本配置 |
自定义存储 | ⭐⭐⭐⭐⭐ | 存储后端、云存储集成 |
文件处理 | ⭐⭐⭐⭐ | 图片处理、文件验证 |
异步上传 | ⭐⭐⭐⭐ | AJAX上传、进度显示 |
二、模型和表单设计
# models.py
from django.db import models
from django.core.validators import FileExtensionValidator
import uuid
import os
def 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_name
def __str__(self):
return self.title
class 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 os
class AliyunOSSStorage(Storage):
"""阿里云OSS存储后端"""
def __init__(self):
self.access_key_id = settings.OSS_ACCESS_KEY_ID
self.access_key_secret = settings.OSS_ACCESS_KEY_SECRET
self.bucket_name = settings.OSS_BUCKET_NAME
self.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 name
def _open(self, name, mode='rb'):
"""从OSS读取文件"""
return self.bucket.get_object(name)
def exists(self, name):
"""检查文件是否存在"""
try:
self.bucket.get_object_meta(name)
return True
except:
return False
def 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 json
def 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 os
def 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 # 5MB
if 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大礼包。最后,祝您早日实现财务自由,还请给个赞,谢谢!