1. 概述

本篇博客讲述如何通过目前最流行的Python Web框架Django制作一个完整的项目实例:在线计算器。该实例前端采用Bootstrap框架完成界面设计和制作,该实例同时支持响应式设计,可以适应手机浏览。具体执行流程为:用户输入计算公式,然后通过Ajax技术将计算内容发送至django后端,然后采用Python的计算模块进行计算,并返回响应结果给前端用来显示。通过该实例的锻炼,可以对Python Web(Django)知识作一个简短的浓缩提炼,巩固基础,同时也方便作为基本组件进行二次开发(可以实现其它在线功能需求)。该实例已部署在百度云服务器上,可以通过访问http://python3web.com:5000/进行访问。代码下载地址:

项目演示效果如下:

python vscode计算器 python web计算器_Bootstrap

输入计算公式,然后单击“计算”按钮,可以输出计算结果。

2. 详细步骤

2.1 环境安装

  • 安装Python3.6.1 

本项目采用Python3开发,具体版本为Pyhthon3.6.1。可以从官网进行下载并安装,下载网址:https://www.python.org/getit/。Python 3的安装比较简单,资料比较多,安装方法可以参考我的博客:,这里不再详细讲解Python安装过程。

  • 安装Django1.11.14

在命令行cmd中输入命令:

pip install django==1.11.14

即可完成安装。注意,Django2系列与Django1系列有一些不同,尤其是配置urls路径的方法,此处推荐Django1系列,它与其它的第三方Python包兼容性更加稳定。

  • 安装编辑器

由于笔者在Windows下进行开发和部署,因此推荐采用微软的编辑器VS Code,使用比较方便,该编辑器目前也比较流行。当然,可以根据使用习惯使用其它编辑器也可,例如sublime等。

2.2 创建项目

在命令工具cmd中输入命令:

django-admin startproject ComputeDemo

创建一个名为ComputeDemo的项目。然后在项目根目录下(manage.py文件所在目录)输入命令:

python manage.py startapp app

创建一个名为app的应用。运行项目查看是否正常,输入:

python manage.py runserver

项目启动后,通过浏览器访问http://127.0.0.1:8000/,项目创建成功界面如下图所示:

python vscode计算器 python web计算器_python vscode计算器_02

2.3 配置并访问页面

完成2.2节中的项目创建后,本小节介绍如何通过django访问自己制作的页面,主要涉及一些基本的django配置操作。首先梳理一下页面访问的基本流程:用户输入网址请求访问页面(例如http://127.0.0.1:8000/),后端服务器收到请求后开始解析网址,根据路由配置文件urls.py中的定义将网址映射到指定的视图文件views.py中的处理函数home,最后由home函数处理请求并返回请求的页面内容index.html。根据上述页面访问流程,下面开始具体的开发。

首先制作用于浏览的页面。

在app文件夹下创建一个templates文件夹,然后在该文件夹下创建一个index.html文件,文件结构图如下:

python vscode计算器 python web计算器_Bootstrap_03

然后编辑html文件用于访问测试,具体代码为:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>在线计算器</title>
</head>

<body>
    <h1>准备制作一款在线计算器</h1>
</body>

</html>

为了测试功能,上述html并没有很复杂,仅仅通过<h1>标签在页面中输出"准备制作一款在线计算器"这一标题文字。

为了能够访问页面,接下来对用户的访问请求进行配置。首先需要将app导入到工程中,打开ComputeDemo文件夹下的settings.py文件,找到INSTALLED_APPS字段,将我们创建的app应用添加进来,代码如下所示:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'app',  #新添加的应用
]

另外,为了后期项目部署和访问方便,需要开放访问权限 ,找到ALLOWED_HOSTS字段,编辑该行代码如下:

ALLOWED_HOSTS = ['*',]

该设置表示对所有访问用户均不设限制。

接下来配置视图处理函数,编辑app文件夹下的views.py文件,代码如下:

from django.shortcuts import render
from django.http import HttpRequest
from django.template import RequestContext

def home(request):
    return render(
        request,
        'index.html',
    )

上述代码在头部额外引入django的两个访问模块用于http访问,然后添加了访问页面对应的home处理函数,在该函数中并没有执行其它的操作,仅仅是返回刚才制作的index.html页面。

最后,配置访问路由即可实现访问。编辑ComputeDemo文件夹下的urls.py文件,代码如下:

from django.conf.urls import url
from django.contrib import admin
import django.contrib.auth.views

import app.views

urlpatterns = [
    url(r'^$', app.views.home, name='home'),
]

上述代码首先导入app文件夹下的views模块,然后通过配置urlpatterns字段实现访问路径和home函数的绑定。

最后在命令行cmd中输入

python manage.py runserver

启动项目,查看效果。访问效果图如下所示:

python vscode计算器 python web计算器_计算器_04

同时按住ctrl+c键可以停止项目的运行。到这里,已经完成了项目的基本准备工作,后面就在这个基础上进行扩展。

2.4 引用Bootstrap进行界面设计

Bootstrap是一款优秀的前端库,通过使用该前端库可以根据需求方便快速的定制出美观的界面。Bootstrap的下载网址为:https://v3.bootcss.com/getting-started/#download。有三种版本可以下载,为了方便后期定制,推荐下载带源码的版本,单击“下载源码”,将文件包下载后解压,找到其中的dist文件夹,该文件夹下有三个子文件夹:css、fonts、js。这三个文件夹即为需要导入的前端库。

在刚才创建的项目app文件夹下面创建一个名为static的文件夹,然后将bootstrap中的css、fonts、js文件夹拷贝到static文件夹下面。另外,为了在static文件夹下新建一个名为img的子文件夹用于存放图片。至此,项目的整体目录结构已经搭建完成,下图是整体的目录结构图:

python vscode计算器 python web计算器_计算器_05

由于我们创建的在线计算器需要采用Ajax发送数据,因此需要导入jquery组件、可以从外部引入该组件,也可以本地引入,这里推荐本地引用方式。从Bootstrap官网的案例上可以找到当前版本的jquery引用网址:https://cdn.jsdelivr.net/npm/jquery@1.12.4/dist/jquery.min.js。打开该网址然后同时按住ctrl+s键进行保存,保存为jquery.min.js文件,由于是js文件,因此将该文件统一的放置在刚才所建static目录中的js文件夹下。

编辑刚才创建的index.html文件,代码如下:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>在线计算器</title>
    {% load staticfiles %}
    <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}" />
    <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}" />
    <script src="{% static 'js/jquery.min.js' %}"></script>
    <script src="{% static 'js/bootstrap.min.js' %}"></script>
</head>

<body>
    <button type="button" class="btn btn-success btn-lg btn_clear" id="lgbut_clear" onclick="fun_clear()">清空</button>
    <button type="button" class="btn btn-primary btn-lg" id="lgbut_compute">计算</button>
</body>

</html>

上述代码中需要说明以下几点:

  • Django中静态文件的引用

之前配置的bootstrap无论是css文件、js文件还是字体库文件,都作为静态文件在html中引用。为了在返回的页面中能够成功引用静态文件,需要采用django的静态模板机制。首先需要导入django的模板静态资源引用标签{% load staticfiles %}。导入标签后,在所有的引用路径前需要添加static标记,即类似:

href="{% static 'app/css/bootstrap.min.css' %}"

这种形式。可以简单的将前面的static标记理解成一种文件映射,即映射到项目app中的static文件夹。通过这种方式就可以正确的引用静态配置文件。如果需要在项目中引用静态图片,也采用这种方式。上述代码在<head>头部主要引用了bootstrap的1个css文件(bootstrap.min.css)和两个js文件(jquery.min.js和bootstrap.min.js),另外,为了一些特殊需求,需要自己定制一些css样式,将所有的样式都放置在style.css文件中并且一样引用进来。在项目的app应用中的static文件夹下找到css文件夹,然后在该文件夹下创建一个style.css文件即可。后面会继续对该文件进行修改。

  • 手机适配

为了能够让我们制作出的网站既能让普通PC电脑浏览器访问,也能适用于手机浏览器访问,需要在头部<head>标签的元标签<meta>中进行配置,通过设定视口viewport和初始访问尺寸initial-scale等使得手机能够友好的进行浏览。

上述代码在body部分引用了两个bootstrap现成的按钮组件,启动项目后效果图如下所示:

python vscode计算器 python web计算器_Django_06

2.5 前端开发

2.5.1 页面制作

本项目制作的在线计算器前端界面设计并不复杂,对照演示效果可以看到共包括:2个文本框组件(1个用于显示计算公式,一个用于显示计算结果)、16个公式编辑按钮(数字、小数点和加减乘除等),2两个逻辑按钮(1个用于清空文本框内容,1个用于执行计算)、1个背景图像。下面进行具体的界面设计。

在前面2.4节的基础上继续编辑index.html文件中的<body>部分,完整代码如下:

<!DOCTYPE html>
<html>

<head>
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <title>在线计算器</title>
   {% load staticfiles %}
   <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}" />
   <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}" />
   <script src="{% static 'js/jquery.min.js' %}"></script>
   <script src="{% static 'js/bootstrap.min.js' %}"></script>
</head>

<body>
   <div class="container-fluid">

      <div class="row">
         <div class="col-xs-1 col-sm-4"> </div>
         <div id="computer" class="col-xs-10 col-sm-6">

            <input type="text" id="txt_code" name="txt_code" value="" class="form-control input_show" placeholder="公式计算"
               disabled />
            <input type="text" id="txt_result" name="txt_result" value="" class="form-control input_show" placeholder="结果"
               disabled />
            <br />
            <div>
               <button type="button" class="btn btn-default btn_num" onclick="fun_7()">7</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_8()">8</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_9()">9</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_div()">÷</button>
               <br />
               <button type="button" class="btn btn-default btn_num" onclick="fun_4()">4</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_5()">5</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_6()">6</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_mul()">x</button>
               <br />
               <button type="button" class="btn btn-default btn_num" onclick="fun_1()">1</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_2()">2</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_3()">3</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_sub()">-</button>
               <br />
               <button type="button" class="btn btn-default btn_num" onclick="fun_0()">0</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_00()">00</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_dot()">.</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_add()">+</button>
            </div>

            <div>
               <br />
               <button type="button" class="btn btn-success btn-lg btn_clear" id="lgbut_clear" onclick="fun_clear()">清空</button>
               <button type="button" class="btn btn-primary btn-lg" id="lgbut_compute">计算</button>
            </div>
         </div>
         <div class="col-xs-1 col-sm-2"> </div>
      </div>
   </div>

   <div class="extendContent">   </div>

</body>

</html>

上述代码通过bootstrap容器对内容空间进行了分配,bootstrap将整个的网页内容分为12个网格,依据这12个网格进行内容的布局并且实现响应式设计(适配手机浏览)。具体的在<body>中首先定义了class="container-fluid"的<div>标签,将网页内容占满整个浏览器窗口,然后对界面元素进行适配布局,其中class=“col-xs-1 col-sm-4”表示该<div>在手机等小屏幕上只占1格,而在电脑等大屏窗口中占4个。外层三个<div>标签基本结构如下:

<div class="col-xs-1 col-sm-4"> 这里置空  用来控制左边距 </div>
<div id="computer" class="col-xs-10 col-sm-6">
     这里用来显示完整的计算器  
</div>
<div class="col-xs-1 col-sm-2"> 这里置空  用来控制左边距 </div>

通过左右<div>尺寸的控制实现响应式布局(手机上需要将计算器放大显示,因此占据网格就多)。

接下来需要对界面进行一点美化和样式控制,编辑css文件夹中的style.css文件(如果之前没有创建的话就创建该文件),代码如下所示:

/*设置整体的背景样式*/
body {
    background-image: url("../img/bg.jpg");
    background-position: center 0;
    background-repeat: no-repeat;
    background-attachment: fixed;
    background-size: cover;
    -webkit-background-size: cover;
    -o-background-size: cover;
    -moz-background-size: cover;
    -ms-background-size: cover; 
}

/*显示文本框样式进行设置*/
.input_show{
    margin-top: 35px;
    max-width: 280px;
    height: 35px;
}

/*数字按钮样式进行设置*/
.btn_num{
    margin: 1px 1px 1px 1px;
    width: 60px;
}

/*清空按钮样式进行设置*/
.btn_clear{
    margin-left: 40px;
    margin-right: 20px;
}

/*用于将背景拉伸,否则在手机上浏览时背景会显示不全*/
.extendContent{
    height: 300px;
}

其中注意在设置body背景时采用了以图片作为默认背景,需要在img文件夹下放置一张名为bg.jpg的图像,并且通过设置    

webkit-background-size: cover;
-o-background-size: cover;
-moz-background-size: cover;
-ms-background-size: cover;

等来满足浏览器的兼容性(为了防止有些浏览器不支持background-size语法)。

启动项目,访问效果如下图所示:

python vscode计算器 python web计算器_python vscode计算器_07

这里注意,有时候有些浏览器的缓存功能会使得css文件的更新不及时,即使已经对css文件进行了编辑并保存,但是界面效果依然没有改变。这种情况下可以对浏览器进行缓存设置,清除其缓存,或者更换浏览器进行浏览。

2.5.2 逻辑功能实现

前端页面的逻辑功能主要分为两部分:

(1)按下数字按钮然后在“公式计算”文本框中显示添加的数字或者运算符号,按下“清空”按钮可以对两个文本框中的数据进行清除。该部分功能主要通过js实现。

具体的,在<body>标签中添加下述js代码:

<script>
      var x = document.getElementById("txt_code");
      var y = document.getElementById("txt_result");

      function fun_7() {
         x.value += '7';
      }

      function fun_8() {
         x.value += '8';
      }

      function fun_9() {
         x.value += '9';
      }

      function fun_div() {
         x.value += '/';
      }

      function fun_4() {
         x.value += '4';
      }

      function fun_5() {
         x.value += '5';
      }

      function fun_6() {
         x.value += '6';
      }

      function fun_mul() {
         x.value += '*';
      }

      function fun_1() {
         x.value += '1';
      }

      function fun_2() {
         x.value += '2';
      }

      function fun_3() {
         x.value += '3';
      }

      function fun_sub() {
         x.value += '-';
      }

      function fun_0() {
         x.value += '0';
      }

      function fun_00() {
         x.value += '00';
      }

      function fun_dot() {
         x.value += '.';
      }

      function fun_add() {
         x.value += '+';
      }

      function fun_clear() {
         x.value = '';
         y.value = '';
      }
   </script>

(2)按下“计算”按钮,将“公式计算”文本框中的数据以Ajax形式发送给后端服务器,同时能够接受后端服务器发回来的执行结果并且显示在“结果”本文框中。之所以采用Ajax技术,是因为该在线计算器项目是以一种非刷新式的方式提交数据并更新结果,这种方式只需要进行局部更新,使得用户体验更加良好。

具体的在<body>标签中添加如下代码:

<script>
      function ShowResult(data) {
         var y = document.getElementById('txt_result');
         y.value = data['result'];
      }
   </script>

   <script>
      $('#lgbut_compute').click(function () {
         $.ajax({
            url: '/compute/', //调用django服务器计算函数
            type: 'POST', //请求类型
            data: {
               'code': $('#txt_code').val()
            }, //将获取文本框中的公式
            dataType: 'json', //期望获得的响应类型为json
            success: ShowResult //在请求成功之后调用该回调函数输出结果
         })
      })
   </script>

上述代码中注意Ajax部分的写法,其中url用来设置请求路径,即访问http://127.0.0.1:8000/compute/完成运算。请求类型为“POST”方式,需要发送的公式通过data字段进行发送,整体的数据类型(通讯协议)采用json字符串形式,最后success字段用来定义请求成功后需要执行的函数,这里我们将请求成功后由后台返回计算的结果通过调用ShowResult函数显示出来。

至此,前端开发部分已经全部完成(其实本质上还是后端开发,因为采用了Django的模板机制,并没有真正做到前后端分离)。

这里给出前端index.html的完整代码:

<!DOCTYPE html>
<html>

<head>
   <meta charset="utf-8">
   <meta http-equiv="X-UA-Compatible" content="IE=edge">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <title>在线计算器</title>
   {% load staticfiles %}
   <link rel="stylesheet" type="text/css" href="{% static 'css/bootstrap.min.css' %}" />
   <link rel="stylesheet" type="text/css" href="{% static 'css/style.css' %}" />
   <script src="{% static 'js/jquery.min.js' %}"></script>
   <script src="{% static 'js/bootstrap.min.js' %}"></script>
</head>

<body>
   <div class="container-fluid">

      <div class="row">
         <div class="col-xs-1 col-sm-4"> </div>
         <div id="computer" class="col-xs-10 col-sm-6">

            <input type="text" id="txt_code" name="txt_code" value="" class="form-control input_show" placeholder="公式计算"
               disabled />
            <input type="text" id="txt_result" name="txt_result" value="" class="form-control input_show" placeholder="结果"
               disabled />
            <br />
            <div>
               <button type="button" class="btn btn-default btn_num" onclick="fun_7()">7</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_8()">8</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_9()">9</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_div()">÷</button>
               <br />
               <button type="button" class="btn btn-default btn_num" onclick="fun_4()">4</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_5()">5</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_6()">6</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_mul()">x</button>
               <br />
               <button type="button" class="btn btn-default btn_num" onclick="fun_1()">1</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_2()">2</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_3()">3</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_sub()">-</button>
               <br />
               <button type="button" class="btn btn-default btn_num" onclick="fun_0()">0</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_00()">00</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_dot()">.</button>
               <button type="button" class="btn btn-default btn_num" onclick="fun_add()">+</button>
            </div>

            <div>
               <br />
               <button type="button" class="btn btn-success btn-lg btn_clear" id="lgbut_clear" onclick="fun_clear()">清空</button>
               <button type="button" class="btn btn-primary btn-lg" id="lgbut_compute">计算</button>
            </div>
         </div>
         <div class="col-xs-1 col-sm-2"> </div>
      </div>
   </div>

   <div class="extendContent">   </div>

   <script>
      var x = document.getElementById("txt_code");
      var y = document.getElementById("txt_result");

      function fun_7() {
         x.value += '7';
      }

      function fun_8() {
         x.value += '8';
      }

      function fun_9() {
         x.value += '9';
      }

      function fun_div() {
         x.value += '/';
      }

      function fun_4() {
         x.value += '4';
      }

      function fun_5() {
         x.value += '5';
      }

      function fun_6() {
         x.value += '6';
      }

      function fun_mul() {
         x.value += '*';
      }

      function fun_1() {
         x.value += '1';
      }

      function fun_2() {
         x.value += '2';
      }

      function fun_3() {
         x.value += '3';
      }

      function fun_sub() {
         x.value += '-';
      }

      function fun_0() {
         x.value += '0';
      }

      function fun_00() {
         x.value += '00';
      }

      function fun_dot() {
         x.value += '.';
      }

      function fun_add() {
         x.value += '+';
      }

      function fun_clear() {
         x.value = '';
         y.value = '';
      }
   </script>

   <script>
      function ShowResult(data) {
         var y = document.getElementById('txt_result');
         y.value = data['result'];
      }
   </script>

   <script>
      $('#lgbut_compute').click(function () {
         $.ajax({
            url: '/compute/', //调用django服务器计算函数
            type: 'POST', //请求类型
            data: {
               'code': $('#txt_code').val()
            }, //将获取文本框中的公式
            dataType: 'json', //期望获得的响应类型为json
            success: ShowResult //在请求成功之后调用该回调函数输出结果
         })
      })
   </script>

</body>

</html>

下面进入基于python的后端开发部分。

2.6 后端开发

后端除了之前已经写好的home函数,还需要处理前端发送过来的计算公式,由python模块执行计算然后将计算结果以json字符串形式返回给前端。因此首先来编写执行计算的处理函数,编辑app文件夹中的views.py文件,添加如下代码:

import subprocess
from django.views.decorators.http import require_POST
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt

def run_code(code):
    try:
        code='print(' + code + ')'
        output = subprocess.check_output(['python','-c',code],universal_newlines=True,stderr=subprocess.STDOUT,timeout=30)
    except subprocess.CalledProcessError as e:
        output = '公式输入有误'
    return output


@csrf_exempt
@require_POST
def compute(request):
    code = request.POST.get('code')
    result = run_code(code)
    return JsonResponse(data={'result':result})

首先注意一下几个导入的模块。头部首先通过import subprocess引入线程模块用于执行发送过来的计算公式。然后引入require_POST来获得后台服务器的POST请求权限(否则发过来的请求会被后台服务器阻止)。接下来引入JsonResponse模块用于将计算得到的结果封装成JSON字符串,最后引入csrf_exempt用于规避csrf校验(用于网站防止被跨站攻击)。

具体的执行函数由compute定义,该函数接收request请求参数,从请求参数中通过request.POST.get('code')获取得到需要计算的公式,然后调用run_code函数执行计算并得到结果,最后由JsonResponse函数将结果进行JSON封装并作为返回值返回。公式执行函数run_code接收字符串code,然后调用线程模块subprocess的check_output函数进行公式计算。

最后,需要对访问路由urls进行配置,编辑ComputeDemo中的urls.py文件,在urlpatterns字段中添加代码。具体如下:

urlpatterns = [
    url(r'^$', app.views.home, name='home'),
    url(r'^compute/$', app.views.compute, name='compute'),  #添加针对compute的路由
]

至此,后端开发全部完成。

运行项目即可看到效果。

2.7 小结

本实例通过一个简短的“在线计算器”应用帮助读者梳理django开发中的基本技术细节,尽管功能有限,但是涵盖了项目开发中的基本步骤和方法。最后,如果需要将项目在云服务器上进行部署可以参考另外两篇博客(全部基于Windows平台):

(1)

(2)

这里不再过多叙述。