引言
本篇参考实验楼中的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项目
首先创建一个myproject
的Django 项目,该目录将包含 Django 项目的基本结构,包括设置文件、URL 配置文件和 WSGI 应用程序文件。命令为:
django-admin startproject myproject
文件树形结构为:
将 templates
和 static
目录放入 myproject/myproject
目录下:
cp -r NewWeb/* myproject/myproject
有了前端的模板与目录文件后,接着就可以进入myproject/settings.py
,添加上其路径与修改其它配置选项:
-
修改允许访问的 IP 地址
我们的实验环境支持一个特殊的随机生成的域名,所以要将 ALLOWED_HOSTS 列表中增加一个匹配任意主机地址的字符串'*'
:ALLOWED_HOSTS = ['*']
-
修改数据库配置项
关于数据库的配置在 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 # 固定端口号
}
}
- 模板文件路径设置
当浏览器发送请求给服务器,服务器派遣对应的视图类处理请求,返回响应需要模板文件提供支持,模板相关的设置在 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',
],
},
},
]
-
静态文件路径设置
模板文件需要调用静态资源进行渲染,静态资源的配置项为 STATICFILES_DIRS 。它是一个列表,里面要写入静态资源的路径,以便 Django 能够找到它们。该配置项并不存在,需要新建:STATICFILES_DIRS = ['myproject/static']
-
设置语言和时区
Django 默认的语言是英文;默认时间使用的协调世界时(UTC),大致等同于我们常说的格林尼治时间(GMT)。我们需要本地化一下。LANGUAGE_CODE = 'zh-Hans' TIME_ZONE = 'Asia/Shanghai'
进入 myproject
文件夹输入命令如下:
cd myproject
python3 manage.py startapp share
这样我们就创建了一个称为 share
的应用,查看它的目录结构:
再次编辑 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 项目之前,我们应该有一个良好的习惯,使用 check 命令检查项目是否有纰漏:
python3 manage.py check
"""
System check identified no issues (0 silenced).
"""
输出上面注释信息就是没问题了,说明Django 项目可以正常启动:
python3 manage.py runserver 0:8080
然后进入页面,因为项目未提供主页路由,会出现如下的Django 欢迎页:
实现文件上传、分享和搜索功能
首先写入模型类:
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
然后根据需求,可以分为四个路由,展示主页,展示文件,用户管理和搜索的四个接口,即:
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
这里就不再详述了,所以可以重新启动进行演示,文件上传为:
而文件管理页面为: