使用Django做一个简单的文件服务器


写在前面

有时候想把资料存在云端,我们可以选择百度云,但是它给的实在太多了,而且下载的时候还限速,我就想要简单的上传下载功能,所以尝试用Django做了一个,在此记录一下。
本文主要讲models和admin的写法,前端只有很少一部分内容。

准备工作

#创建项目
django-admin startproject myproject
#创建app
python manage.py startapp uploader

修改配置

settings.py
添加以下路径:

#修改
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'uploader',#添加app
]
#修改
TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [os.path.join(BASE_DIR,'templates')],#主要是这一行
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]
#以下为添加的部分
#静态文件路径
STATIC_URL = '/static/'
STATICFILES_DIRS = (
    os.path.join(BASE_DIR,'static'),
)
#上传文件的保存路径
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR,'media/')

定义models

uploader/models.py:

from django.db import models
from django.utils import timezone

# Create your models here.
class Uploader(models.Model):
    #文件id,作为主键
    id = models.AutoField(primary_key=True)
    #文件标题,可以理解为文件的描述
    title = models.CharField(verbose_name = '标题',max_length = 100)
    #文件主体,在后面定义保存的路径,通过日期来区分
    file = models.FileField(verbose_name='文件', upload_to='uploader/files/%Y%m%d/', blank=True)
    #上传时间
    created = models.DateTimeField(verbose_name = '创建时间',default = timezone.now)
    #文件大小
    size = models.CharField(verbose_name='大小',max_length=50,blank=True)
    #上传者
    author = models.ForeignKey(
        'auth.User',
        verbose_name='用户',
        on_delete=models.CASCADE,
        default=1)
    #为了后台上传时也能计算文件大小,重写save方法
    def save(self,*args,**kwargs):
        size = round(self.file.size / 1024 ** 2, 2)
        self.size = str(size) + "MB" if size < 1024 else str(round(size / 1024, 2)) + "GB"
        #大小计算完毕后重新将文件保存
        super(Uploader, self).save(*args,**kwargs)

    class Meta:
        #文件排序方式,上传时间倒序
        ordering = ('-created',)
        #自定义model名称
        verbose_name = '附件'
        verbose_name_plural = verbose_name

    def __str__(self):
        #显示文件标题
        return self.title

定义admin

uploader/admin.py:

from django.contrib import admin
from .models import Uploader
from django.utils.translation import ugettext_lazy as _
from django.utils.html import format_html
import os
from django.db.models.signals import post_delete
from django.dispatch import receiver
from django.conf import settings
# Register your models here.
#以下为自定义过滤器,通过上传者进行过滤
#写法参考本站的后端代码,这里没有注释
class ImageFilter(admin.SimpleListFilter):
    title = _("用户")
    parameter_name = 'author'

    def lookups(self, request, model_admin):
        authors = list(set(map(lambda x: x.author, Uploader.objects.all())))
        for author in authors:
            yield author.id, _(author.username)

    def queryset(self, request, queryset):
        id_ = self.value()
        if id_:
            return queryset.filter(author__id__exact=id_)
        else:
            return queryset

#自定义admin后台
class UploaderAdmin(admin.ModelAdmin):
    #显示的信息,download_link为下载链接
    list_display = ('id','title','author','created','size','download_link')
    #可以作为超链接的选项(转向model的更改页面)
    list_display_links = ('id','title')
    #过滤器,通过创建时间过滤
    list_filter = [ImageFilter,'created']
    #修改页面显示的内容
    fields = ['title','author','created','file']

    #下载链接,将其渲染为HTML
    def download_link(self, obj):
        return format_html('<a href="%s">%s</a>' % (obj.file.url, '下载'))
    #据说allow_tags在django2.x以上被停用了,但是我这里用的还可以
    #如果遇到问题可以使用mark_safe
    download_link.allow_tags = True
    download_link.short_description = '下载'

    #定义一个方法,当文件在后台被删除时一并删除服务器中的资源
    @receiver(post_delete, sender=Uploader)
    def delete_upload_files(sender, instance, **kwargs):
        files = getattr(instance, 'file')
        if not files:
            return
        fname = os.path.join(settings.MEDIA_ROOT, str(files))
        if os.path.isfile(fname):
            os.remove(fname)
#将model注册到后台
admin.site.register(Uploader,UploaderAdmin)

使用

首先迁移数据库:
python manage.py makemigrations
python manage.py migrate 创建超级用户:
python manage.py createsuperuser
启动服务:
python manage.py runserver 访问:
127.0.0.1:8000/admin/

前端使用

myproject/urls.py:

from django.contrib import admin
from django.urls import path,include
from django.conf import settings
from django.conf.urls.static import static
urlpatterns = [
    path('admin/', admin.site.urls),
    path('upload/',include('uploader.urls',namespace ='upload')),
]
urlpatterns += static(settings.MEDIA_URL,document_root = settings.MEDIA_ROOT)

uploader/forms.py:

from django import forms
from .models import Uploader

class UploadForm(forms.ModelForm):
    class Meta:
        model = Uploader
        fields = ('title','file')

uploader/views.py:

from django.shortcuts import render,redirect
from django.contrib.auth.decorators import login_required
from .forms import UploadForm
from django.contrib.auth.models import User
from django.http import HttpResponse
@login_required(login_url='/admin/')
def uploadview(request):
    if request.method == 'POST':
        upload_form = UploadForm(request.POST,request.FILES)
        if upload_form.is_valid():
            new_upload = upload_form.save(commit = False)
            new_upload.author = User.objects.get(id = request.user.id)
            new_upload.save()
            return HttpResponse("上传成功")
        else:
            return HttpResponse("表单内容有误,请重新填写")
    else:
        upload_form = UploadForm()
        context = {'upload_form':upload_form}
        return render(request,'uploader/file.html',context = context)

uploader/urls.py:

from django.urls import path,include
from . import views

app_name = 'uploader'
urlpatterns = [
    path('files/',views.uploadview,name='files'),
]

编写模版:

根目录下新建templates文件夹,其中新建uploader文件夹:
templates/base.html:

<!DOCTYEP html>
{% load static %}
<html lang="zh-cn">
<head>
    <meta charset="UTF-8">
    <title>{% block title %}{% endblock %}</title>
    <link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap.min.css' %}">
    <nav class="navbar navbar-expand-lg navbar-dark bg-dark">
      <div class="container">
        {% block item %}{% endblock item %}
      </div>
    </nav>
</head>
<body>
    <script src="{% static 'jquery/jquery-3.5.1.js' %}"></script>
    <script type="text/javascript" src="{% static 'bootstrap/js/bootstrap.min.js' %}"></script>
    <div id="wrapper">
        {% block content %}{% endblock content %}
        <div od="push"></div>
    </div>
    {% block script %}{% endblock script %}
</body>

templates/uploader/file.html:

{% extends "base.html" %}
{% load static %}
{% block title %}
        上传文件
{% endblock title %}
{% block item %}
    <p class="navbar-brand">上传文件</p>
    <div>
      <ul class="navbar-nav">
        <li class="nav-item">
          <a class="nav-link" href="{% url 'upload:files' %}">上传</a>
        </li>
      </ul>
    </div>
{% endblock item %}
{% block content %}
<style type="text/css">
    .upload{
    position:absolute;
    left:30px;
    top:220px;
    }
</style>
<div class="container">
    <div class="row">
        <div class="col-4"></div>
        <div class="col-8">
            <br>
            <form method="post" action="." enctype="multipart/form-data">
                {% csrf_token %}
                <div class="form-group col-md-4">
                        <label for="title">描述</label>
                        <input type="text" class="form-control" id="title" name="title">
                </div>

                <div class="form-group col-md-4">
                    <label for="file">文件</label>
                    <input type="file" class="form-control-file" name="file" id="file">
                </div>
                <br>
                <div class="upload">
                    <button type="submit" class="btn btn-primary" style="width:220px;">上传</button>
                </div>
            </form>
        </div>
    </div>
</div>
{% block script %}
{% endblock script %}
{% endblock content%}

bootstrap和jquery等文件可以去官网下载。。真的需要的话,也可以给我发邮件,我通过邮箱发给你。

写在后面

以上内容,后端应该是OK的,至于前端,我没有仔细检索,我那个项目里有两个app,我只选择这一部分粘贴了过来,前端可能有跟另一个app交叉的部分。
如果功能再次优化,我会将其传到github,到时候会附上地址。