一個學期即將過去,我們也迎來了2018年。這個學期,首次接觸了web網站開發建設,不僅是這門課程,還有另外一門用idea的gradle框架來制作網頁。
很顯然,用python語言的flask框架更加簡單易懂。Python不需要像Java語言一樣設置類的類型,也沒有gradle框架那么多的各層依賴,所以說Python更加容易上手。
初次接觸網頁開發,對我來說就像是打開了新世界的大門,無論是前端還是后端都有它有意思的地方,每次你寫的網頁成功實現你想要的效果的時候,都會有一種成就感。這種成就感也許也是做這一行的人繼續坐下去的動力之一吧。
下面是我的Python+Flask+MysqL的web建設技術過程。
Python+Flask+MysqL的web建設技術過程
1 建立flask框架項目
首先是flask安裝(這里默認已經安裝好python了),這里我們學習的項目只是簡單的用pip install flask的語句安裝了flask庫,如果你碰到多個項目並且有不同的python版本或外部依賴庫,可以參考:http://docs.jinkan.org/docs/flask/installation.html
開發工具上使用了pycharm作為工具,建立項目首先新建項目,選擇flask框架進行構建。
2 導航及CSS
在網頁前端開發的過程中,我引用了bootstrap的前端架構(其實只用了一些組件)。具體的bootstrap學習可以參考菜鳥教程:http://www.runoob.com/bootstrap/bootstrap-tutorial.html
首先先建立前端html頁面,頁面導航通過標簽<nav>進行編寫。
<!-- 頂部導航 --> <nav class="navbar navbar-default" role="navigation"> <div class="navsize"> <ul class="nav nav-tabs"> <li class="active"><a href="#">首頁</a></li> <li><a href="#">論壇</a></li> <li><a href="#">客戶端</a></li> <li><a href="#">投稿</a></li> <ul class="nav navbar-nav navbar-right"><li><a href="#"><span class="glyphicon glyphicon-user"></span> 注冊</a></li> <li><a href="#"><span class="glyphicon glyphicon-log-in"></span> 登錄</a></li></ul> <form class="navbar-form navbar-right" role="search" action="{{ url_for("search") }}" method="get"> <div class="form-group"> <input type="text" class="form-control" name="q" placeholder="Search"> </div> <button type="submit" class="btn btn-default"> 搜索 </button> </form> </ul> </div> </nav>
.navsize{ width: 80%; margin:0 auto; }
效果:
3 圖片導航CSS實例
首先要學會使用div等塊級元素標簽來放置我們的網頁元素,並會使用CSS選擇器進行網頁的開發。
這里先了解一下CSS的盒子模型:
margin:盒子的最外層,一般用於設置與其他元素的間隔距離
border:盒子的邊框層,可以根據自己的需要設置是否可見及大小、顏色等
padding:盒子放置的內容與邊框的中間層,可用於設置隔開內容及邊框
content:盒子放置的內容層
圖片導航實例:
<div> <div class="img"> <a href="http://www.gzcc.cn"><img src="http://old.bz55.com/uploads/allimg/150316/140-150316134616.jpg"></a> <div class="desc"><a href="http://www.gzcc.cn">第一張圖片</a></div> </div> </div> <div class="clearfloat"> <img src="http://old.bz55.com/uploads/allimg/150316/140-150316134616.jpg"> <img src="http://www.pp3.cn/uploads/201701/2017011709.jpg"> <img src="http://old.bz55.com/uploads/allimg/150316/140-150316134610.jpg"> <img src="http://old.bz55.com/uploads/allimg/150316/140-150316134613.jpg"> </div>
div.img{ width: 180px; border:1px solid #eeeeee; float: left; margin:5px; } div.img img{ width: 100%; height: auto; } div.desc{ text-align: center; padding: 5px; } div.clearfloat{ clear: both; } div.img:hover{ border: 1px solid blue; }
上面CSS語句中:
float:left是設置塊級元素不換行並且向左邊浮動,會根據瀏覽器大小進行浮動
clear:both是用於清除該塊級元素與同一級上一個塊級元素之間的浮動
hover:鼠標在該元素上時的樣式,用於與鼠標不在元素上時的樣式作為對比,形成一種動態效果
效果:
4 使用JS對登錄、注冊操作進行表單驗證
JavaScript是現在各種網站十分常用的網頁技術,動態的加載及相應請求等。
這里以登錄及注冊為例,可以用JavaScript來要求頁面上登錄賬號及注冊賬號的輸入,對數據的使用及存入數據庫進行初步規范。
下面是登錄功能的js代碼:
function Login(){ var un=document.getElementById("id"); var us=document.getElementById("password"); var er=document.getElementById("error_box"); er.innerHTML = "<br>"; if(un.value.length<6||un.value.length>20){ er.innerHTML="用戶名必須在6-20個字符之間"; return false; }else if((un.value.charCodeAt(0))>=48 && un.value.charCodeAt(0)<=57){ er.innerHTML = "首字母不能為數字" return false; }else for(var i=0;i<un.value.length;i++){ if((un.value.charCodeAt(i)<48)||(un.value.charCodeAt(i)>57)&&(un.value.charCodeAt(i)<97)&&(un.value.charCodeAt(i)>122)){ er.innerHTML="用戶名只能為數字和字母" return false; } } if(us.value.length<6||us.value.length>20){ er.innerHTML="密碼必須在6-20個字符之間"; return false; } return true; }
<input type="submit" class="btn btn-default" onclick="return Login()" value="登陸"></input>
在登錄按鈕中引用js函數進行應用,這里注意一個問題,需要在函數名前面增加return,否則即使輸入的數據有誤,js文件雖然會執行,但並不會攔截在登錄頁面,而是依舊會跳轉到登錄后的界面,注冊功能甚至會直接把數據存進數據庫中。這個問題我在idea中並沒有碰到,而在這個項目中卻會出現,原因我現在也不是很清楚,初步判斷應該是后面添加的一些功能與其起了沖突,或者flask框架的一些不同。
效果圖:
5 父模板制作
我們在編寫多個頁面的時候,會發現有許多地方是會用到一樣的設計的,比如導航就是每頁都有並且是一樣的。
我們可以將其中相同的地方抽取出來,做一個父模板,然后讓子頁面繼承父模板,就不需要多寫許多相同的代碼,減少了工作量,也使得代碼更加簡潔。
下面是我的父模板:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>{% block title %}{% endblock %}</title> <!-- 引入 Bootstrap --> <link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"> <link href="{{ url_for("static",filename="CSS/bass.css") }}" type="text/css" rel="stylesheet" charset="UTF-8"> {% block head %}{% endblock %} </head> <body class="bgimg"> <!-- 頂部導航 --> <nav class="navbar navbar-default" role="navigation"> <div class="navsize"> <ul class="nav nav-tabs"> <li class="active"><a href={{ url_for("index") }}>首頁</a></li> <li><a href="#">論壇</a></li> <li><a href="#">客戶端</a></li> <li><a href={{ url_for("tougao") }}>投稿</a></li> <ul class="nav navbar-nav navbar-right"> {% if username %} <li><a href="{{ url_for('author',user_id=useraction.id,tag=1) }}"><span class="glyphicon glyphicon-user"></span> {{ username }}</a></li> <li><a href={{ url_for("logout") }}><span class="glyphicon glyphicon-log-out"></span> 注銷</a></li> {% else %} <li><a href={{ url_for("register") }}><span class="glyphicon glyphicon-user"></span> 注冊</a></li> <li><a href={{ url_for("login") }}><span class="glyphicon glyphicon-log-in"></span> 登錄</a></li> {% endif %} </ul> <form class="navbar-form navbar-right" role="search" action="{{ url_for("search") }}" method="get"> <div class="form-group"> <input type="text" class="form-control" name="q" placeholder="Search"> </div> <button type="submit" class="btn btn-default"> 搜索 </button> </form> </ul> </div> </div> </div> </nav> <div class="body"> {% block main %}{% endblock %} </div> <!-- 底部導航 --> <nav class="navbar navbar-default navbar-fixed-bottom" role="navigation"> <div class="navsize"> <ul class="nav navbar-nav"> <li class="active"><a href="#">友情鏈接</a></li> <li><a href="#">聯系我們</a></li> <li><a href="#">加入我們</a></li> <li><a href="#">關於我們</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><a href="#"> 幫助中心</a></li> <li><a href={{ url_for('fankui') }}> 反饋中心</a></li> </ul> </div> </nav> </body> </html>
子頁面:
{% extends 'base.html' %} {% block title %} 反饋中心 {% endblock %} {% block main %} <div class="divtouming"> <div class="panel panel-default" style="width: 800px"> <div class="panel-body"> <form class="form-horizontal" role="form"> <div class="form-group"> <label for="fankuititle" class="col-sm-2 control-label">反饋原因</label> <div class="col-sm-10"> <input type="text" class="form-control" id="fankuititle"> </div> </div> <div class="form-group"> <label for="fankuitext" class="col-sm-2 control-label">詳情</label> <div class="col-sm-10"> <textarea id="fankuitext" rows="5" cols="87"></textarea> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-default">提交</button> </div> </div> </form> </div> </div></div> {% endblock %}
在父模板中,通過添加 {% block title %}{% endblock %} 標簽來連接父模板與子頁面的關系,父模板中的 {% block title %}{% endblock %} 標簽位置就是子頁面的繼承父模板后的代碼插入位置。父模板的繼承用 {% extends 'base.html' %} 這樣的標簽來繼承。
並且也可以看到將后端的數據渲染到模板中則是使用 {{name}} 這樣的形式,里面是變量名,用兩個花括號包着。
6 flask項目開始
下面是一個簡單的開始項目(后端):
from flask import Flask,render_template
url_for('static',filename='js/login.js')
app = Flask(__name__) @app.route('/') def index(): return render_template("base.html") @app.route('/login') def login(): return render_template("login.html") @app.route('/regis') def register(): return render_template("Zhuce.html") if __name__ == '__main__': app.run(debug=True)
app = Flask(__name__) 即初始化一個flask實例對象。
app.run(debug=True) 將debug模式打開,是程序員測試時用的模式,真正使用項目的時候需要把這個模式關掉。
@app.route("/") 在運行實體上綁定URL路由
render_template("Zhuce.html") 返回一個模板文件
url_for('static',filename='js/login.js') 獲取“static”文件夾下的靜態資源
7 連接mysql數據庫,創建用戶模型
首先,需要安裝好python+flask、MySQL。然后需要根據python的版本下載好MySQL和python的中間件。
下載中間件后用 pip install filename的方法將文件安裝到python的Script文件夾中,然后創建一個數據庫。
另外寫一個config.py的配置文件,寫入下面兩行代碼:
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:數據庫密碼@數據庫地址:3306/數據庫名稱?charset=utf8' SQLALCHEMY_TRACK_MODIFICATIONS = False
然后在主py文件中導入config文件並初始化對象:
import config from flask import Flask,render_template from flask_sqlalchemy import SQLAlchemy app = Flask(__name__) app.config.from_object(config) db=SQLAlchemy(app)
開始創建用戶模型,即在數據庫中創建表格:
class User(db.Model): __tablename__='user' id = db.Column(db.Integer,primary_key=True,autoincrement=True) username=db.Column(db.String(20),nullable=False) password=db.Column(db.String(20),nullable=False) db.create_all()
db.create_all()用來執行db的建表操作
8 通過用戶模型對數據庫進行增刪該查
class User(db.Model): __tablename__='user' id = db.Column(db.Integer,primary_key=True,autoincrement=True) username=db.Column(db.String(20),nullable=False) password=db.Column(db.String(20),nullable=False) #創建表格 db.create_all() #數據查詢方法 User.query.filter(User.username == 'mis1114').first() #數據添加方法 user=User(username='mis777789',password='g6666') db.session.add(user) db.session.commit() # 數據的修改方法 user = User.query.filter(User.username=='mis777789').first user.password='0.0.0.0' db.session.commit() # 數據的刪除方法 user = User.query.filter(User.username=='mis777789').first() db.session.delete(user) db.session.commit()
9 完成注冊及登陸功能
- js文件: onclick函數return True時才提交表單,return False時不提交表單。
- html文件:
- <form>中設置 action和method="post"
- <input> 中設置 name
- 主py文件中:
- from flask import request, redirect, url_for
- @app.route('/regist/', methods=['GET', 'POST’])
注冊:
@app.route('/regis',methods=['GET','POST']) def register(): if request.method=='GET': return render_template('Zhuce.html') else: username=request.form.get('zcid')#與html頁面名字相同 password=request.form.get('zcpassword') user=User.query.filter(User.username==username).first() if user: return 'exit' else: user=User(username=username,password=password) db.session.add(user) db.session.commit() return redirect(url_for('login'))
登陸:
從flask中導入session,在config配置中加入SECRET_KEY=os.urandom(24),將登陸的用戶存入session對象中。
@app.route('/login',methods=['GET','POST']) def login(): if request.method == 'GET': return render_template('login.html') else: username = request.form.get('id') # 與html頁面名字相同 password = request.form.get('password') user = User.query.filter(User.username == username,User.password==password).first() if user: sessions['username']=username sessions.permanent=True return redirect(url_for('index')) else: return '用戶不存在'
登陸與注冊的HTML頁面:
<form class="bs-example bs-example-form" role="form" action="{{ url_for('login') }}" method="post">
10 登陸之后更新導航
在父模板中使用IF函數判斷是否有登陸對象:
<ul class="nav navbar-nav navbar-right"> {% if username %} <li><a href="#"><span class="glyphicon glyphicon-user"></span> {{ username }}</a></li> <li><a href={{ url_for("logout") }}><span class="glyphicon glyphicon-log-in"></span> 注銷</a></li> {% else %} <li><a href={{ url_for("register") }}><span class="glyphicon glyphicon-user"></span> 注冊</a></li> <li><a href={{ url_for("login") }}><span class="glyphicon glyphicon-log-in"></span> 登錄</a></li> {% endif %} </ul>
主py文件中,通過使用上下文處理器,將獲取的session中的值以字典的形式返回頁面:
@app.context_processor def mycontext(): username=session.get('user') if username: return {'username':username} else: return {}
11 發布功能
登陸裝飾器:
def log(func):#參數類型為函數 @wraps(func) def wrapper(*args,**kwargs): if session.get('user'): return func(*args,**kwargs) else: return redirect(url_for('login')) return wrapper
裝飾器應用:
應用裝飾器,要求在發布前進行登錄,登錄后可發布。
@app.route('/tougao',methods=['GET','POST']) @log def tougao():
完成發布功能:
@app.route('/tougao',methods=['GET','POST']) @log def tougao(): if request.method=='GET': return render_template('tougao.html') else: title=request.form.get('tougaotitle') content=request.form.get('tougaotext') id=User.query.filter(User.username==session.get('user')).first().id tougao=Tougao(title=title,content=content,userid=id) db.session.add(tougao) db.session.commit() return redirect(url_for('index'))
12 首頁列表顯示全部問答
首頁HTML代碼:
<div class="container"> <div class="row clearfix"> <div class="col-md-2 column"> </div> <div class="col-md-6 column"> <ul> {% for item in touGao %} <a href="" class="list-group-item list-group-item-light"> <li> <div class="page-header"> <h3>{{ item.title }}</h3> <p><span class="glyphicon glyphicon-user">{{ item.author.username }}</span> <span class="glyphicon glyphicon-time">{{ item.time }}</span></p> </div> <p>{{ item.content}}</p> </li> </a> {% endfor %} </ul> </div> <div class="col-md-4 column"> </div> </div> </div>
主py文件:
@app.route('/') def index(): context={ 'touGao':Tougao.query.order_by('-time').all() } return render_template("index.html",**context)
13從首頁投稿到文章詳情頁
主PY文件寫視圖函數,帶id參數。
@app.route('/pinglun/<question_id>') def detial(question_id): quest=Question.query.filter(Question.id==question_id).first() return render_template('commentdetail.html',que=quest)
首頁標題的標簽做帶參數的鏈接。
<a href="{{ url_for('commentdetail',question_id=foo.id) }}">{{ foo.title}}</a>
在詳情頁將數據的顯示在恰當的位置。
{% for foo in question %} <li class="list-group-item"> <span class="glyphicon glyphicon-leaf" aria-hidden="true"></span> <a href="{{ url_for('commentdetail',question_id=foo.id) }}">{{ foo.title}}</a> <p style="...">{{ foo.detail}}</p> <span class="glyphicon glyphicon-leaf" aria-hidden="true"></span> <span class="badge">{{ foo.creat_time}}</span> </li> {% endfor %}
建立評論的對象關系映射:
class Question(db.Model): __tablename__ = 'comment' id =db.Column(db.Integer,primary_key=True,autoincrement=True) author_id=db.Column(db.Integer,db.ForeignKey('uesr.id')) question_id=db.Column(db.Integer,db.ForeignKey('question.id')) creat_time=db.Column(db.DateTime,default=datetime.now) detail=db.Column(db.Text,nullable=False) question =db.relationship('Question',backref=db.backref('comments')) author=db.relationship('User',backref=db.backref('comments'))
14 完成評論功能
定義評論的視圖函數,讀取前端頁面數據,保存到數據庫中。
@app.route('/pinglun',methods=['POST']) @log def pinglun(): pl=request.form.get('pingluntext') tougao_id=request.form.get('tougaoid') user_id=User.query.filter(User.username==session.get('user')).first().id pinglun=Pinglun(userid=user_id,tougaoid=tougao_id,content=pl) db.session.add(pinglun) db.session.commit() context={ 'pingLun':Pinglun.query.order_by('-time').all() } return render_template(url_for('digital',tougao_id=tougao_id),**context)
顯示所有評論:
{% extends "base.html" %} {% block title %}標題{% endblock %} {% block main %} <div class="container"> <div class="row clearfix"> <div class="col-md-12 column divcolor" > <div class="page-header"> <h1> {{ quest.title }} </h1> <small><a href="{{ url_for('author',user_id=quest.author.id) }}"><span class="glyphicon glyphicon-user">{{ quest.author.username }}</span></a> <span class="glyphicon glyphicon-time">{{ quest.time }}</span></small> </div> <p> {{ quest.content }} </p> <hr> <form class="form-horizontal" role="form" action="{{ url_for('pinglun') }}" method="post"> <div class="form-group"> <label for="toutaotext" class="col-sm-2 control-label">評論</label> <div class="col-sm-10"> <textarea id="pingluntext" rows="5" cols="100" name="pingluntext"></textarea> <input name="tougaoid" type="hidden" value="{{ quest.id }}" id="tougaoid"> </div> </div> <div class="form-group"> <div class="col-sm-offset-2 col-sm-10"> <button type="submit" class="btn btn-success">評論</button> </div> </div> </form> <h4>評論({{ quest.pinglun|length }}):</h4> <ul> {% for pl in quest.pinglun %} <li class="list-group-item"> <p><a href="{{ url_for('author',user_id=pl.author.id) }}"><span class="glyphicon glyphicon-user"></span>{{ pl.author.username }}</a></p> <p class="text-right"><span class="label label-default">{{ pl.time }}</span></p> <h4>{{ pl.content }}</h4> </li> {% endfor %} </ul> </div> </div> </div> {% endblock %}
15 個人中心頁標簽導航
新頁面authorbase.html,用<ul ><li role="presentation"> 實現標簽頁導航。
<ul class="nav nav-tabs">
<li role="presentation"><a href="#">Home</a></li>
<li role="presentation"><a href="#">Profile</a></li>
<li role="presentation"><a href="#">Messages</a></li>
</ul>
authorbase.html繼承base.html。重寫title,head,main塊.將上述<ul>放在main塊中.定義新的塊user。
讓上次作業完成的個人中心頁面,繼承user.html,原個人中心就自動有了標簽頁導航。
制作個人中心的三個子頁面,重寫authorbase.html中定義的user塊。
authorbase:
{% extends "base.html" %} {% block head %}{% endblock %} {% block main %} <div class="container"> <div class="row clearfix divcolor"> <div class="col-md-8 column"> <h2>{{ user.username }}</h2> <p>投稿總數:{{ tougao|length }} 評論數量:{{ pinglun|length }}</p> <hr> <ul class="nav nav-pills" role="tablist"> <li class="active"><a href="#">全部投稿</a></li> <li><a href="#">全部評論</a></li> <li><a href="#">用戶信息</a></li> </ul> <br> {% block author %}{% endblock %} </div> <div class="col-md-4 column"> </div> </div> </div> {% endblock %}
author1:
{% extends "authorbase.html" %} {% block title %}個人中心{% endblock %} {% block head %}{% endblock %} {% block author %} <div> <ul> {% for tg in tougao %} <a href="{{ url_for('digital',tougao_id=tg.id) }}" class="list-group-item list-group-item-light"> <li> <div class="page-header"> <h3>{{ tg.title }}</h3> <p><span class="glyphicon glyphicon-user">{{ tg.author.username }}</span> <span class="glyphicon glyphicon-time">{{ tg.time }}</span></p> </div> </li> </a> {% endfor %} </ul> </div> {% endblock %}
像上面這個繼承authorbase編寫三個不同的子網頁,然后寫一個視圖函數帶標簽頁面參數tag,從而達到點擊點擊不同按鈕有不同的頁面的效果。
@app.route('/author/<user_id>/<tag>') def author(user_id,tag): user=User.query.filter(User.id==user_id).first() context={ 'user':user, 'tougao':user.tougao, 'pinglun':user.pinglun } if tag=='1': return render_template('author1.html', **context) elif tag=='2': return render_template('author2.html', **context) else: return render_template('author3.html', **context)
導航標簽鏈接增加tag參數:
<small><a href="{{ url_for('author',user_id=quest.author.id,tag=1) }}"><span class="glyphicon glyphicon-user">{{ quest.author.username }}</span></a> <span class="glyphicon glyphicon-time">{{ quest.time }}</span></small>
有鏈接到個人中心頁面的url增加tag參數:
<a href="{{ url_for('usercenter',user_id = session.get('userid'), tag=1) }}">{{ session.get('user') }}</a>
16 實現搜索功能
准備視圖函數search():
from sqlalchemy import or_ @app.route("/search") def search(): q=request.args.get("q") tg=Tougao.query.filter( or_( Tougao.title.contains(q), Tougao.content.contains(q) ) ).order_by("-time") return render_template("index.html",touGao=tg)
修改base.html 中搜索輸入框所在的:
<form class="navbar-form navbar-right" role="search" action="{{ url_for("search") }}" method="get"> <div class="form-group"> <input type="text" class="form-control" name="q" placeholder="Search"> </div> <button type="submit" class="btn btn-default"> 搜索 </button> </form>
17 密碼保護
1.更新User對象,設置對內的_password
class User(db.Model): __tablename__='user' id = db.Column(db.Integer,primary_key=True,autoincrement=True) username=db.Column(db.String(20),nullable=False) _password=db.Column(db.String(200),nullable=False)
2.編寫對外的password
from werkzeug.security import generate_password_hash, check_password_hash @property def password(self): #外部使用,取值 return self._password @password.setter def password(self, row_password):#外部使用,賦值 self._password = generate_password_hash(row_password)
3.密碼驗證方法:
def check_password(self, row_password): #密碼驗證 result = check_password_hash(self._password,row_password) return result
4.登錄驗證:
password1 = request.form.get('password') user = User.query.filter(User.username == username).first() if user: if user.check_password(password1):