Python web用Django开发一个完整的论坛/影评系统(附移动端)并通过Nginx+uWSGI部署
上一篇博客写到如何在Debian linux系统下搭建nginx+uwsgi环境,并且在项目中还有一个Django开发的证券系统,本来打算将二者结合一下尝试部署一个后端用Django的web项目,后来想想还是要重新搞一个,毕竟证券系统那个只算是一个移植项目,只是合并了一下遗留Oracle数据库,并且较为浅薄的用了一下Django,通过更加深入的学习,我又弄了一个完全用Django开发,仅使用原生的sqlite数据库,更复杂的操作,并且在移动端方面采用前后端分离,及API技术来实现,最后通过Nginx+uWsgi部署到服务器上,即如果有付费的服务器就可上线的网站。
这里给出项目的源代码[firstsite](/download/Django firstsite.zip)
一、关于Django
在之前的证券系统项目中有介绍,关于安装,目录结构,MTV模式等,详情参照证券系统。
二、前端页面(T)层,运用SemanticUI的css样式模板
1.Semantic UI是语义化的前端 UI 框架,包含 50 多个组件。
官方解释:Semantic允许开发人员快速构建漂亮的网站,简洁的HTML,直观的javascript和简化的调试,有助于使前端开发成为一种愉快的体验。语义是响应性设计,允许您的网站在多个设备上扩展。Semantic是生产就绪的,并且与React,Angular,Meteor和Ember等框架相结合,这意味着您可以将它与任何这些框架集成,以便与应用程序逻辑一起组织UI层。
简单来说就是一个快速开发网页的框架,它有简洁美观大方的样式,并且使用起来非常的简单。
2.安装Semantic UI
这里将以两种方式讲解如安装 Semantic-UI,其中第一种方式仅要求读者理解最基本的 HTML 语法。
1)简单的在header中引用
如果您使用Semantic UI作为依赖项并且只想使用我们的默认主题,使用semantic-ui-css或semantic-ui-lessrepo。如果您只需要这些文件,可以从GitHub下载它们。
对于初学者来说,node, npm, gulp 等工具会造成不少麻烦,即陷入所谓的“依赖地狱”(dependency hell)。如果你不想用这些工具,可以简单地将 Semantic-UI 预编译好的 CSS 和 JavaScript 文件加入到 HTML 的
I.下载预编译好的压缩包
然后查看下载到的压缩包,你只需用到其中这些文件:
.css和.js文件为主要的样式文件,然后上面两个文件夹中themes为用到的字体,图片的文件,components为一些特殊的样式文件,带有.min的应该为精简版,即应该是不带报错提示的那种,不带.min的为开发版,这点应该与Vue.js类似。
之后将这些文件放到一个文件夹中塞入你的项目文件,并通过header引入
1 | <link rel="stylesheet" href="./css/semantic.css" charset="utf-8"> |
像这样,简单的引用过后,即可食用Semantic UI的默认主题样式了,为什么多引入了一个jquery,这是因为如果你要使用 Semantic-UI 涉及 JavaScript 的高级功能,比如 tab, progress, sticky, API 等,就必须加上 jQuery 库,这是 Semantic-UI 所需要的全部依赖。jquery.js很容易在网上下载到,或者引用链接,这里我就不多述了。
2)通过npm安装并用gulp build
如果你已经有一些前端开发的经验,至少对 npm 感到不陌生, 并听说过 gulp,那么在你的项目中使用 Semantic-UI 将变得更加便捷。
I.安装gulp
首先默认你已经安装好npm,然后安装gulp
1 | npm install -g gulp |
II.Npm项目初始化
在你的项目路径下打开命令行
1 | npm init |
III.安装 Semantic-UI
1 | npm install —-save semantic-ui |
根据网速情况,可能会花几分钟到十几分钟不等。安装完成后
IV.编译 Semantic-UI
1 | npm install semantic-ui --save |
之后就会在semantic的dist中出现我之前说过的文件夹及文件了,然后引入原理相同,这么安装只是可以修改主题,详情请参考官方文档,之后的语义用法也在其中,如果你对npm不熟悉,没用过node.js,建议用第一种方式。
3.使用Semantic-UI
使用起来也是非常的简单,如果仅是用css样式的话,只需要在需要用标签中堆叠class即可,就像为其加入修饰词
1 |
|
像这样即可快速写出精美的按钮及菜单样式
更多用法参照官方文档,建议用谷歌浏览器查看,自带谷歌翻译。
三、PC端的开发
1.主页面(index)的开发
I.html页面开发
使用SemanticUI快速开发出需要的页面架构,并在头部加入模板语言以适应Django的交互
1 | {% load staticfiles %} |
II.通过get请求实现文章的分类/分页
1 | a_tag = request.GET.get('tag') |
在models.py中的article对象中加入tag属性,即相当于article表中增加tag一列,并判断网页get请求是否带有tag参数来对应但会不同的数据装填如context字典中,page分页为每页6条内容,若出现参数超过页数默认返回最后一页,参数不为整数,默认返回第一页,在前端实现分别对应下拉框选择类别,左右按钮翻页(如果为最后一页则向后翻页按钮变灰,第一页向左翻页按钮变灰)。
然后对应的article的每列数据显示在页面上,需要说的这里显示的people got it为article设置的likes,假数据,然后后续detail中显示的为投票功能的结果数据,为真数据。views同样为后台设置。 new,editors为装饰的假按钮,在移动端中设置了其作用。
2.登录注册页面(register_login)的开发
需要说明的是登录注册为统一模板渲染,只不过装填的表单不同,但是分配的不同的url,所以有些固定样式会重复出现,即图中框选部分,分别对应两个页面的跳转。
1 | from django.contrib.auth.forms import UserCreationForm, AuthenticationForm |
实现原理也很简单,分别用的django自带的UserCreationForm, AuthenticationForm,判断不同情况渲染不同的表单装填入页面,值得一提的是django自带的这两种表单都有错误判别的功能,即遇到错误会有提示,用户不存在,注册两遍密码不一致等,这也放入了前端样式实现错误提示,给用户更良好的交互。
3.文章详情页(detail)的开发
主要有的功能是评论提交显示,best评论,及like,dislike的投票选择的功能实现。
1 | belong_to = models.ForeignKey(to=Article, related_name="under_comments", null=True, blank=True,on_delete=models.CASCADE) |
在评论里加入belong_to外键,best_comment布尔类型来控制评论属于哪篇文章,以及是否是best_comment。需要注意的是django更新到2.0后外键属性里面必须要有on_delete属性:主外关系键中,级联删除,也就是当删除主表的数据时候从表中的数据也随着一起删除。
on_delete参数的各个值(可选的值都内置在django.db.models中)的含义:
- CASCADE:级联删除,模拟SQL语言中的ON DELETE CASCADE约束,将定义有外键的模型对象同时删除!
- PROTECT: 保护模式,阻止上面的删除操作,但是弹出ProtectedError异常
- SET_NULL:置空模式,将外键字段设为null,只有当字段设置了null=True时,方可使用该值。
- SET_DEFAULT: 置默认值,将外键字段设为默认值。只有当字段设置了default参数时,方可使用。
- DO_NOTHING:什么也不做。
- SET():设置为一个传递给SET()的值或者一个回调函数的返回值,注意大小写。自定义一个值,该值当然只能是对应的实体。
通过id来识别每篇文章,comment的表单通过django的forms创建,并新建forms.py文件设置约束。也会把报错渲染在前端。
投票的逻辑是:票有两个外键非别对应用户和文章,有三个状态like,dislike,normal,然后先判断是否登陆,登陆后才能投票,继续判断用户是否投过票,如果没有先创建票,如果投过了根据新投的来更改票的状态。前端对应的样式为选中like/dislike变亮,再次点击变灰。
然后再detail中的people got it的值是根据
1 | like_counts = Ticket.objects.filter(choice='like', video_id=id).count() |
对应该文章下,选择为like的票的数量,为真实值。
4.关于用户头像/文章封面的图片上传
1 | url_image = models.URLField(null=True, blank=True, default='') |
有两种方式,一种为网络图片链接(URLFiled),另一种为本地上传(FileFiled)
图片链接就直接填入链接对应的字符串即可,之后若是想要展示需要有网。
本地上传中upload_to为对应上传到的图片路径,即在django的admin后台点击上传文件,save保存后,会在upload_to对应的项目路径下添加一个与上传文件同名的文件,然后cover的值为upload_to+文件名
若要在前端能展示还需要经过一些步骤
1)在settings.py中添加如下两行
1 | UPLOAD_ROOT = os.path.join(BASE_DIR,'upload').replace('\\','/') |
2)再urls.py中添加
1 | from django.conf import settings |
这样你就可以通过localhost:8000/upload/来访问到upload文件夹下的文件了
配合cover的值来使用,就可以实现可视化了。注意是端口8000后面的第一层链接,视对应情况前面添加../或../../。
三、移动端的开发(前后端分离)
1.前端实现(Vue.js+reqwest.js)
1) Vue.js
Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用提供驱动。
简单来说就是一个可以让你更方便操作使用js的框架,官方文档
I.安装Vue.js
与SemanticUI类似,Vue.js同样支持两种方式安装引入,一种是直接下载官方文件,另一种是npm安装(具体详见官方文档)
然后放到你的项目中并引用
1 | <script src="./js/vue.js"></script> |
II.使用Vue.js及vue-devtools
1 |
|
测试以上代码,并将Vue.js路径更改为你的路径,在浏览器测试即可看到Hello Vue.js!字样,怎么样是不是与Django在templates层的模板变量表示方式一致。
安装vue-devtools以更直观的调试查看。有三种方式可供安装,具体详见链接。谷歌的需在扩展插件界面添加(需要梯子),火狐点击后可直接安装。以下展示两种安装后的效果。
谷歌安装成功后,在运行有Vue.js渲染的程序的时候V标志变亮,并可以通过Vue查看
火狐安装成功后效果与谷歌大体相同,展示的为linux虚拟机上的火狐浏览器。
通过vue-devtools可是实时查看页面中数据的变化。
1 |
|
以上代码对Vue的使用简单做了下示范,可以实现动态放大和缩小文章字体,实时监控输入框还可以输入多少文字,屏蔽不喜欢的评论,点击按钮弹窗功能。
放大缩小功能实现
- 首先在data中定义好fontSize变量,注意需为整数
- 然后设置按钮对应调整字体大小的功能,对应绑定事件例如字体放大5px
1
v-on:click="fontSize+=5"
- 最后在需要放大字体的地方加入绑定样式即可
1
<p :style="{fontSize: article.fontSize +'px'}"></p>
在Vue 1.x版本中可以这样写
1 | <p style="fontSize: {{article.fontSize}} px"></p> |
直接通过绑定变量的方法即可实现,但是在Vue 2.x后更新了用法需要用v-bind:,简写为:
引号里如果只有变量或者对象可直接写为
1 | :class = "变量名" |
具体情况详见Class 与 Style 绑定
监控还可以输入多少文字
1 | <h3 class="ui header"> 你还可以输入 {{ 200 - message.length }} 字 </h3> |
通过v-model来绑定输入框,监测数据变化。具体介绍可以参考官方文档
弹窗功能
这里的实现方式有两种,一种是自己定义样式实现弹窗效果,另一种是基于SemanticUI中的modal样式,自定义指令来实现。
1.自定义样式
1 |
|
这就实现了自定义样式点击按钮弹窗,点击空白部分弹窗关闭。
2.通过SemanticUI样式自定义指令
代码正如我给的样例一样,由于SemanticUI中的models里面的动态效果是通过jequery来调用渲染的,不支持Vue.js,所以如果要在Vue.js中使用,需要自定义指令。
1 | Vue.directive ("modal", { |
这就是自定义了一个v-modal的指令,并导入jquery来进行引入。同时还需要导入semantic.js文件。
要注意的是modal自带的效果就是弹框,点击空白部分隐藏,所以这里用的v-if和v-on:click的命令组合弹框过后需要点两次才能再次弹框。
以上简单介绍了Vue.js的一些用法,具体可以参照官方文档
另外如果你了解vue.js 1.x版本的语法,在2.x中有一些改进,比如2.x不允许绑定body标签了
在1.x中你可以操作body中的所有标签
1 | <body id="app"> |
在2.x中你必须新定义一个新的标签
1 | <body> |
在div#app中定义需要管理的标签
1.x中的ready在2.x中用mounted替换
1.x中的过滤器,在2.x中也移除了
具体详细的变更请参考从 Vue 1.x 迁移
2) reqwest.js和API
reqwest 用于浏览器异步HTTP请求。支持xmlHttpRequest, JSONP, CORS, 和 CommonJS约束。
简单来说就是一种使用ajax的方式,语法更简单清晰。配合Vue.js使用可以达到前后端分离的效果。
更多用法可以查看github的介绍。
为了方便这里我直接给出reqwest.js以及后续用到的js.cookie.js,点击下载
API是与因特网相连的端系统提供得一个应用程序接口(英语:Application Programming Interface,缩写:API;又称为应用程序编程接口)是软件系统不同组成部分衔接的约定。
我理解的就是:后端,数据库将数据打包成json(一般为json)格式,并留出一个接口(网址)来供前端/其他项目调用。像百度就提供了人脸识别,语音识别的一些API。
来看一个reqwest获取api的例子
首先给一个星球大战API的网址,SWAPI然后通过reqwest获取people/1/的信息
1 |
|
运行后,打开控制台,输入a,可以看到获取到的people/1/的数据全部存在了a中
3) Vue.js+reqwest.js渲染获取到的API
1 |
|
运行后页面即显示出加载的数据,其中需要一步var self=this是因为reqwest为函数中的函数所以要调用上层数据需要这一步,mounted为2.x版本的写法,1.x中为ready意思是加载页面之前执行的函数。
最后这里给出一个功能比较完善的加载文章评论界面detail
2.Django中通过rest_framework将数据渲染成API
I.首先先安装djangorestframework模块
1 | pip install -i https://pypi.tuna.tsinghua.edu.cn/simple djangorestframework |
用清华的源,速度快
II.在settings.py中INSTALLED_APPS配置中加入两行
1 | INSTALLED_APPS = [ |
III.在settings.py配置Django REST framework的默认认证模式!!!这块非常重要不然你会疯狂被CSRF Failed报错折磨,蛇皮跨站攻击验证…
1 | REST_FRAMEWORK = { |
IV.migrate生成数据表
1 | python manage.py makemigrations |
V.新建api.py写出api视图的逻辑
1 | from firstapp.models import Article |
写一个序列化器将Article数据表中的数据转化成字典的形式,fields参数可以是一个元组,为要渲染成api的字段例如fields = (‘title’,’content’,),注意最后的逗号,写为all则渲染所有字段。
1 |
|
取出所有数据用序列化器转化为字典,然后用装饰器来限定为get请求,即返回json数据。
VI.设置链接接收api数据
1 | from firstapp.api import article |
之后运行访问localhost:8000/api/Article即可查看到api数据
3.前端通过reqwest.js访问api链接调用数据
1 | getData:function(){ |
由于在同一个域名下,只需添加后面的部分即可访问取得数据
然后再在前端页面通过Vue.js管理数据,即可实现前后端分离的效果
4.通过TokenAuthentication来实现用户登录,权限控制
1 | from rest_framework.authtoken import views |
通过post请求localhost:8000/api/token-auth,传入用户名密码数据便可获取到相应的token,然后将token放入cookies,之后就可以通过token来验证用户身份了。
至于如何发送post请求,就有很多种办法了,这里依旧用reqwest.js来实现
1 | logIn:function () { |
在Vue的methods中添加loginIn函数,就实现了通过admin1管理员登录的功能。
如果在上一步没有配置Django REST framework的默认认证模式,这里查看network就会出现
{“detail”:”CSRF Failed: CSRF token missing or incorrect.”}
的response返回值
这是因为DRF使用的默认SessionAuthentication方案。 DRF的SessionAuthentication使用Django的会话框架进行身份验证,需要检查CSRF。
当您在视图/视图集中未定义任何authentication_classes时,DRF将此认证类用作默认值。
由于DRF需要支持对相同视图的基于会话和非会话的认证,因此它仅对经过身份验证的用户实施CSRF检查。这意味着只有经过身份验证的请求需要CSRF令牌,并且可以在没有CSRF令牌的情况下发送匿名请求。如果您使用带有SessionAuthentication的AJAX样式API,则需要为任何“不安全”HTTP方法调用(如PUT,PATCH,POST或DELETE请求)包含有效的CSRF令牌。
更改默认认证模式即可解决
反正csrf这种东西是个坑,但它又能很好的防止跨站攻击。
然后可以通过判断用户是否登陆来返回不一样的数据,比如未登录只能查看五条内容,并通过判断用户身份来确认用户是否有删除修改某篇文章的权限。
5.通过localhost:8000/m/index来访问移动端界面
需要说明的是这里的文章分类是通过前端筛选实现的,根据chozen的值不同来渲染出不同的文章。然后通过v-on:click来改变chozen的值。选择editors choice可以对文章进行加精和删除操作,注意只有文章的作者或管理员才可以删除相应的文章。点击右下角编写的图标可以实现文章的编写,封面为默认封面。
四、Nginx + uWsgi部署
之前的博客介绍了如何进行环境的配置Debian kali linux x64下Python + Django + Nginx + uWSGI环境配置
1.运行uwsgi命令测试django项目
1 | uwsgi --http :8888 --chdir firstsite/ --module firstsite.wsgi:application --static-map=/static=static |
1 | uwsgi |
2.创建uwsgi配置文件
在项目根目录下创建mysite_uwsgi文件夹,里面创建uwsgi.ini文件
1 | [uwsgi] |
其中socket可以使文件路径也可以是ip,只要与nginx对应配置的一致即可
3.配置nginx
根据目录结构的不同,配置的方式也不同,如这种目录结构
可以看出,首先有一个总的配置nginx.conf,包含错误日志的路径,然后里面写到
1 | include /etc/nginx/conf.d/*.conf; |
即包含conf.d文件下的所有配置,然后conf.d文件夹下有一个默认配置文件default.conf
里面默认的端口为80,由于被Apache所占用(配置环境那篇博客提到)所以我改为808端口,即启动服务后808端口为nginx欢迎界面。
接下来配置mysite.conf文件
1 | upstream django { |
这里是通过ip来与uwsgi连接的,而且包含uwsgi_params。
正常访问uwsgi需要一个uwsgi_params的文件,在编译安装的目录中有这个文件,可以直接使用。如果没有可以从 github 获取
然后把这个文件复制到firstsite工程目录中。之后再include对应的路径。
其中的127.0.0.1:8888可以用django代替,upstream django为django优先使用为server的端口。
或者可以通过.sock文件来连接
1 | # 指定项目路径uwsgi |
总之保证这里的uwsgi_pass和uwsgi里面的socket配置的可以匹配即可。
最后8080为暴露在外面供调用的端口
附:如果为另一种结构,直接将默认配置文件备份后替换成mysite.conf即可
4.配置静态文件路径
在项目中的settings.py中添加如下两行,并在根目录创建static文件夹对应nginx中/static后配置的路径。
1 | STATIC_ROOT = os.path.join(BASE_DIR, "static/") |
然后运行来收集静态文件以便nginx管理
1 | python manage.py collectstatic |
5.nginx+uwsgi管理项目
首先运行uwsgi配置
1 | uwsgi --ini firstsite/mysite_uwsgi/uwsgi.ini |
检查uwsgi是否启动成功
1 | ps -aux | grep uwsgi |
如果结果中有多个进程,我们就可以看到它启动成功了。
然后运行nginx
1 | sudo service nginx start |
分别对应启动,重启,重载,停止
如果启动失败可以查看端口是否被占用,强制杀死端口所有进程
1 | lsof -t -i tcp:8080 |
通过
1 | ps -ef | grep nginx |
可以查看nginx运行情况
也可以通过firstsite/mysite_uwsgi/uwsgi.log,以及nginx对应的error_log路径来查看运行情况。
如果都没有问题的话就可以通过localhost:8080来查看你的django项目了。
6.简单说一下原理
Nginx是一款高性能的 Web和 反向代理 服务器,也是一个 IMAP/POP3/SMTP 代理服务器。在高连接并发的情况下,Nginx是Apache服务器不错的替代品。
- nginx是对外的服务器,外部浏览器通过url访问nginx。
- uwsgi是对内的服务器,主要用来处理动态请求。
nginx接收到浏览器发送过来的http请求,将包进行解析,分析url,
a.如果是静态文件请求就直接访问用户给nginx配置的静态文件目录,直接返回用户请求的静态文件
b.如果不是静态文件,而是一个动态的请求,那么nginx就将请求转发给uwsgi
uwsgi接收到请求之后将包进行处理,处理成wsgi可以接受的格式,并发给wsgi,wsgi根据请求调用应用程序的某个文件,某个文件的某个函数,最后处理完将返回值再次交给wsgi,wsgi将返回值进行打包,打包成uwsgi能够接收的格式,uwsgi接收wsgi发送的请求,并转发给nginx,nginx最终将返回值返回给浏览器。
五、网站上线
以上操作全都在虚拟机中完成,如果想上线你就可以买一个服务器和域名,获取到公网ip然后将域名解析,绑定公网ip,然后再服务器上配置好环境,并部署,启动之后,除非你关闭服务器,不然的话,就可以一直通过公网IP访问到网站,绑定域名后就可通过域名访问。
附:如果有什么疑问,或遇到什么问题可以通过评论或右下角的信息标志给我留言,我会及时回复。