引言

本篇参考实验楼中的django打造文件共享系统,结合之前自己的经验,重新回顾django这个框架,在这之前,距离我上一次使用django,已经过去了快3年了。所以本篇涉及到的很多基础概念都是初级,为了复习而用。

项目所需环境

sudo pip3 install pip -U
sudo pip3 install django==3.2.7

sudo apt update
sudo apt install libmysqlclient-dev
sudo pip3 install mysqlclient

前端原始资源上传至了csdn,突然发现改变很大,上传信息随便填了下,之后如果看的人多再改云盘吧,zip文件是已经全部修改好的版本,也就是只要专注后端,解压后的目录结构如下图所示:
django复习笔记:一个简单的文件分享系统-LMLPHP

启动一个django项目

首先创建一个myproject 的Django 项目,该目录将包含 Django 项目的基本结构,包括设置文件、URL 配置文件和 WSGI 应用程序文件。命令为:

django-admin startproject myproject

文件树形结构为:
django复习笔记:一个简单的文件分享系统-LMLPHP
templatesstatic 目录放入 myproject/myproject 目录下:

cp -r NewWeb/* myproject/myproject

有了前端的模板与目录文件后,接着就可以进入myproject/settings.py ,添加上其路径与修改其它配置选项:

  1. 修改允许访问的 IP 地址
    我们的实验环境支持一个特殊的随机生成的域名,所以要将 ALLOWED_HOSTS 列表中增加一个匹配任意主机地址的字符串 '*'

    ALLOWED_HOSTS = ['*']
    
  2. 修改数据库配置项
    关于数据库的配置在 DATABASES 字典里,默认的数据库是 Sqlite3 这个单机版极简数据库。我们要使用 MySQL 数据库替代它,修改 DATABASES 如下:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.mysql',   # 引擎由 Django 提供
        'NAME': 'myproject',                    # 数据库的名字
        'USER': 'root',                         # 用户默认为 root
        'PASSWORD': '',                         # 没有设置密码
        'HOST': '127.0.0.1',                    # 本地连接,固定 IP
        'PORT': 3306                            # 固定端口号
    }
}
  1. 模板文件路径设置
    当浏览器发送请求给服务器,服务器派遣对应的视图类处理请求,返回响应需要模板文件提供支持,模板相关的设置在 TEMPLATES 列表下,该列表中默认有一个字典对象,其中的 DIRS 字段的值是列表,我们需要将模板文件的相对路径的字符串写入其中,也就是 ‘myproject/templates’ 。修改结果如下:
	TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': ['myproject/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',
            ],
        },
    },
]
  1. 静态文件路径设置
    模板文件需要调用静态资源进行渲染,静态资源的配置项为 STATICFILES_DIRS 。它是一个列表,里面要写入静态资源的路径,以便 Django 能够找到它们。该配置项并不存在,需要新建:

    STATICFILES_DIRS = ['myproject/static']
    
  2. 设置语言和时区
    Django 默认的语言是英文;默认时间使用的协调世界时(UTC),大致等同于我们常说的格林尼治时间(GMT)。我们需要本地化一下。

    LANGUAGE_CODE = 'zh-Hans'
    TIME_ZONE = 'Asia/Shanghai'
    

进入 myproject 文件夹输入命令如下:

cd myproject
python3 manage.py startapp share

这样我们就创建了一个称为 share 的应用,查看它的目录结构:
django复习笔记:一个简单的文件分享系统-LMLPHP
再次编辑 myproject/settings.py 文件,修改 INSTALLED_APPS 列表,该配置项用于注册应用,将 'share' 字符串写入其中:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'share',
]

最后创建数据库,这里我是做了一个mysql的镜像,启动后进入容器进行创建数据库,首先拉取mysql镜像启动并进入:

docker run --net=host --name cmysql -e MYSQL_ROOT_PASSWORD=123456 -d mysql

docker exec -it  cmysql bash

然后创建:

mysql -uroot -p

CREATE SCHEMA myproject CHARSET = UTF8

针对每个 Django 项目,都有一些项目必需的数据表,执行如下命令创建它们的迁移文件并在 myproject 数据库中创建它们:

python3 manage.py makemigrations
python3 manage.py migrate

操作截图如下:

django复习笔记:一个简单的文件分享系统-LMLPHP
在启动 Django 项目之前,我们应该有一个良好的习惯,使用 check 命令检查项目是否有纰漏:

python3 manage.py check
"""
System check identified no issues (0 silenced).
"""

输出上面注释信息就是没问题了,说明Django 项目可以正常启动:

python3 manage.py runserver 0:8080

然后进入页面,因为项目未提供主页路由,会出现如下的Django 欢迎页:
django复习笔记:一个简单的文件分享系统-LMLPHP

实现文件上传、分享和搜索功能

首先写入模型类:

from django.db import models
from datetime import datetime


class Upload(models.Model):
    '''上传文件使用的映射类
    '''

    # 访问页面的次数
    downloadcount = models.IntegerField(verbose_name='访问次数', default=0)
    # 该字段作为一个文件的唯一标识
    code = models.CharField(verbose_name='code', max_length=8)
    # 文件上传时间
    datetime = models.DateTimeField(verbose_name='上传时间',
            default=datetime.now())
    # 文件存储路径
    path = models.CharField(verbose_name='存储路径', max_length=64)
    # 文件名
    name = models.CharField(verbose_name='文件名', max_length=32)
    # 文件大小
    filesize = models.CharField(verbose_name='文件大小', max_length=8)
    # 上传文件的客户端的 IP 地址
    pcip = models.CharField(verbose_name='IP 地址', max_length=16)

    # 这个方法用于格式化类的实例的打印样式,便于测试
    def __str__(self):
        return self.name

模型类中的常用字段类:

  • CharField (字符串)
  • IntegerField (整数)
  • DateTimeField (常用的时间字段)
  • TextField (大容量文本字段)

字段属性:

  • default = 0 设置默认值
  • max_length = 23 设置字段长度最大值
  • min_length = 5 设置字段长度最小值
  • verbose_name = “” 指明了字段一个易于理解的名字

重新进行数据迁移:

python3 manage.py makemigrations
python3 manage.py migrate

django复习笔记:一个简单的文件分享系统-LMLPHP

然后根据需求,可以分为四个路由,展示主页,展示文件,用户管理和搜索的四个接口,即:

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', HomeView.as_view(), name='home'),
    path('s/<code>', DisplayView.as_view(), name='display'),
    path('my/', MyView.as_view(), name='my'),
    path('search/', SearchView.as_view(), name='search'),
]

最后是根据路由来构建视图函数,为:

class HomeView(TemplateView):
    '''用来展示主页的视图类
    '''

    template_name = 'base.html'

    def post(self, request):
        # 如果表单中有文件
        if request.FILES:
            file = request.FILES.get('file')
            name = file.name
            size = int(file.size)
            path = 'myproject/static/file/' + name
            with open(path, 'wb') as f:
                f.write(file.read())
            code = ''.join(random.sample(string.digits, 8))
            upload = Upload(
                    path = path,
                    name = name,
                    filesize = size,
                    code = code,
                    pcip = str(request.META['REMOTE_ADDR'])
            )
            upload.save()
            return HttpResponsePermanentRedirect("/s/"+code)

class DisplayView(ListView):
    '''展示文件的视图类
    '''

    def get(self, request, code):
        uploads = Upload.objects.filter(code=code)
        if uploads:
            for upload in uploads:
                upload.downloadcount += 1
                upload.save()
        return render(request, 'content.html', {'content': uploads, 'host': request.get_host()})

class MyView(ListView):
    '''用户管理视图类,就是用户管理文件的那个页面的视图类
    '''

    def get(self, request):
        ip = request.META['REMOTE_ADDR']
        uploads = Upload.objects.filter(pcip=ip)
        for upload in uploads:
            upload.downloadcount += 1
            upload.save()
        return render(request, 'content.html', {'content': uploads})

class SearchView(ListView):
    '''搜索功能的视图类
    '''

    def get(self, request):
        code = request.GET.get('kw')
        u = Upload.objects.filter(name__icontains=str(code))
        data = {}
        if u :
            # 将符合条件的数据放到 data 中
            for i in range(len(u)):
                u[i].downloadcount += 1
                u[i].save()
                data[i]={}
                data[i]['download'] = u[i].downloadcount
                data[i]['filename'] = u[i].name
                data[i]['id'] = u[i].id
                data[i]['ip'] = str(u[i].pcip)
                data[i]['size'] = u[i].filesize
                data[i]['time'] = str(u[i].datetime.strftime('%Y-%m-%d %H:%M'))
                # 时间格式化
                data[i]['key'] = u[i].code
        # django 使用 HttpResponse 返回 json 的标准方式,content_type 是标准写法
        return HttpResponse(json.dumps(data), content_type="application/json")

而因为这里做的是前后端不分离的实验,所以后端写完,还需要改写前端页面,但目前来讲,不分离的项目已经很少了,这里着重在搜索的ajax代码上,因为搜索框内容需要前端来进行判断和甄别,具体代码为:

// /static/js/index.js
        ...
$('.search-text-icon').click(function() {

    if( !$("#files-info").val() || $("#files-info").val().length < 4 ) {
        // 控制搜索内容长度
        $("#files-info").focus().val("").attr("placeholder", "搜索内容不能为空或长度小于4个字符");

    } else {

    var op = {
        "method" : "get",  // 请求类型为 GET 请求
        "url" : "/search/",  // 向/search 请求
        "data": "kw=" + $("#files-info").val(),  // 获取数据
        "success" : function(data) {  // s 回调函数
                console.log(data);
        addSearchFile(data);
            },
        "error" : function(error) {
            console.log(error);
        }

        };
        ...

其余的根据jinja2语法,无非就是根据后端逻辑将内容嵌入到页面上,进行重新渲染,以展示文件为例,展示文件信息使用的模板文件是 myproject/templates/content.html,它继承了 base.html 文件,现将其优化如下:

{% extends "base.html" %} {% block content %} {% for i in content %}
<div class="file-content">
  <div class="file-name" style="width:25%;">{{ i.name }}</div>
  <div class="file-size" style="width:11%;">{{ i.filesize }} B</div>
  <div class="file-date" style="width:5%;">{{ i.downloadcount }}</div>
  <div class="file-date" style="width:16%;">{{ i.datetime }}</div>
  <div
    class="file-link"
    style="width:43%; padding-right: 80px; text-overflow: ellipsis; overflow: hidden;"
  >
    <span> https://{{ host }}/s/{{ i.code}} </span>
  </div>
  <a
    href="/{{ i.path|cut:'myproject/' }}"
    style="color:#FFF;"
    download="{{ i.name }}"
    ><button>Download</button></a
  >
</div>
{% endfor %} {% endblock %}

在视图类中,后端通过ORM查询到数据库中信息,通过render进行重定向并将这通过content字段返回,前端通过jinja2语法进行了渲染,具体的更多说明可以参考jinja2的文档:

http://doc.yonyoucloud.com/doc/jinja2-docs-cn/index.html

这里就不再详述了,所以可以重新启动进行演示,文件上传为:

django复习笔记:一个简单的文件分享系统-LMLPHP

而文件管理页面为:

django复习笔记:一个简单的文件分享系统-LMLPHP

04-06 03:55