每天40分玩转Django:Django文件上传

Django文件上传

一、今日学习内容概述

学习模块 重要程度 主要内容
基础文件上传 ⭐⭐⭐⭐⭐ 文件字段、基本配置
自定义存储 ⭐⭐⭐⭐⭐ 存储后端、云存储集成
文件处理 ⭐⭐⭐⭐ 图片处理、文件验证
异步上传 ⭐⭐⭐⭐ AJAX上传、进度显示

二、模型和表单设计

python 复制代码
# 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

三、自定义存储后端

python 复制代码
# 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'

四、文件处理视图

python 复制代码
# 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})

五、文件上传流程图

六、模板实现

html 复制代码
<!-- 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 %}

七、文件验证器

python 复制代码
# 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')

八、异步上传实现

javascript 复制代码
// 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);
    }
});

通过本章学习,你应该能够:

  1. 实现基本的文件上传功能
  2. 自定义存储后端
  3. 处理文件验证和安全
  4. 实现异步文件上传

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

相关推荐
网安-轩逸1 小时前
IPv4地址表示法详解
开发语言·php
play_big_knife1 小时前
鸿蒙项目云捐助第二十八讲云捐助项目首页组件云数据库加载轮播图
数据库·华为·harmonyos·鸿蒙·云开发·鸿蒙开发·鸿蒙技术
qq_321665332 小时前
mysql 数据库迁移到达梦数据库
数据库·mysql
浊酒南街3 小时前
决策树python实现代码1
python·算法·决策树
Hello.Reader3 小时前
Redis大Key问题全解析
数据库·redis·bootstrap
FreedomLeo14 小时前
Python机器学习笔记(十三、k均值聚类)
python·机器学习·kmeans·聚类
星光樱梦4 小时前
32. 线程、进程与协程
python
阿正的梦工坊4 小时前
深入理解 PyTorch 的 view() 函数:以多头注意力机制(Multi-Head Attention)为例 (中英双语)
人工智能·pytorch·python
西猫雷婶5 小时前
python学opencv|读取图像(十九)使用cv2.rectangle()绘制矩形
开发语言·python·opencv
靖顺5 小时前
【OceanBase 诊断调优】—— packet fly cost too much time 的根因分析
数据库·oceanbase