Flask 構建微電影視訊網站(四)
後臺管理
實現後臺管理系統使用flask sqlalchemy結合mysql資料庫進行增刪改查操作、分頁的使用、路由裝飾器定義、模板中變數呼叫、登入會話機制、上傳檔案、flask wtforms表單使用。
管理員登入
對models
進行重構,將資料庫的配置資訊放在app/__init__.py
檔案中
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'mysql+mysqlconnector://root:[email protected] :3306/movie'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = True
app.config['SECRET_KEY'] = 'cb34xxxxxxxxxxxxxxxxxxbae30d90f6'
db = SQLAlchemy(app)
在models
檔案中直接引入db
from app import db
定義登陸表單欄位
app/admin/forms.py
from flask_wtf import FlaskForm from wtforms import StringField, PasswordField, SubmitField from wtforms.validators import DataRequired, ValidationError from app.models import Admin class LoginForm(FlaskForm): ''' 管理員登陸表單 ''' account = StringField( label='賬號', validators=[ DataRequired("請輸入賬號!") ], description="賬號", render_kw={ "class": "form-control", "placeholder": "請輸入賬號!", "required": "required" } ) pwd = PasswordField( label='密碼', validators=[ DataRequired("請輸入密碼!") ], description="密碼", render_kw={ "class": "form-control", "placeholder": "請輸入密碼!", "required": "required" } ) submit = SubmitField( '登入', render_kw={ "class": "btn btn-primary btn-block btn-flat", } ) def validate_account(self, field): account = field.data admin = Admin.query.filter_by(name=account).count() if admin == 0: raise ValidationError("賬號不存在!")
render_kw
裡的樣式是前端程式碼中的
編寫試圖函式
# 裝飾器用來進行訪問控制 def admin_login_req(func): @wraps(func) def decorated_function(*args, **kwargs): if session.get('admin', None) is None: return redirect(url_for('admin.login', next=request.url)) return func(*args, **kwargs) return decorated_function
@admin.route('/login/', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
data = form.data
admin = Admin.query.filter_by(name=data['account']).first()
if not admin.check_pwd(data['pwd']):
flash("賬號或密碼錯誤! ")
return redirect(url_for('admin.login'))
session['admin'] = data['account']
return redirect((request.args.get('next') or url_for('admin.index')))
return render_template('admin/login.html', form=form)
@admin.route('/logout/')
@admin_login_req
def logout():
session.clear()
return redirect(url_for('admin.login'))
在每一個需要進行登陸才能操作的檢視函式中加入裝飾器,像logout
檢視一樣
在Admin
模型中新增密碼校驗函式
def check_pwd(self, pwd):
from werkzeug.security import check_password_hash
return check_password_hash(self.pwd, pwd)
修改模板app/templates/admin/login.html
<div class="login-box-body">
{% for message in get_flashed_messages() %}
<p class="login-box-msg" style="color: red">{{ message }}</p>
{% endfor %}
<form action="" method="post" id="form-data">
<div class="form-group has-feedback">
{{ form.account }}
<span class="glyphicon glyphicon-envelope form-control-feedback"></span>
{% for err in form.account.errors %}
<div class="col-md-12" id="input_user" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group has-feedback">
{{ form.pwd }}
<span class="glyphicon glyphicon-lock form-control-feedback"></span>
{% for err in form.pwd.errors %}
<div class="col-md-12" id="input_pwd" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="row">
<div class="col-xs-8">
</div>
<div class="col-xs-4">
{{ form.submit }}
{{ form.csrf_token }}
</div>
</div>
</form>
</div>
標籤管理
建立一個表單form
app/admin/forms.py
class TagForm(FlaskForm):
name = StringField(
label='名稱',
validators=[
DataRequired("請輸入標籤!")
],
description="名稱",
render_kw={
"class": "form-control",
"id": "input_name",
"placeholder": "請輸入標籤名稱!"
}
)
submit = SubmitField(
'編輯',
render_kw={
"class": "btn btn-primary"
}
)
新增標籤
檢視函式
@admin.route('/tag/add/', methods=['GET', 'POST'])
@admin_login_req
def tag_add():
form = TagForm()
if form.validate_on_submit():
data = form.data
tag = Tag.query.filter_by(name=data['name']).count()
if tag == 1:
flash("標籤已存在!", 'error')
return redirect(url_for('admin.tag_add'))
tag = Tag(
name=data['name']
)
db.session.add(tag)
db.session.commit()
flash("標籤新增成功!", 'info')
return redirect(url_for('admin.tag_add'))
return render_template('admin/tag_add.html', form=form)
修改前端程式碼
<form role="form" method="post">
<div class="box-body">
{% for message in get_flashed_messages(category_filter=['info']) %}
<div class="alert alert-success alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
</button>
<h4><i class="icon fa fa-check"></i> 操作成功!</h4>
{{ message }}
</div>
{% endfor %}
{% for message in get_flashed_messages(category_filter=['error']) %}
<div class="alert alert-danger alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
</button>
<h4><i class="icon fa fa-ban"></i> 操作失敗!</h4>
{{ message }}
</div>
{% endfor %}
<div class="form-group">
<label for="input_name">{{ form.name.label }}</label>
{{ form.name }}
{% for err in form.name.errors %}
<div class="col-md-12" id="input_user" style="color: red">{{ err }}</div>
{% endfor %}
</div>
</div>
<div class="box-footer">
{{ form.submit }}
{{ form.csrf_token }}
</div>
</form>
標籤列表
檢視函式
@admin.route('/tag/list/<int:page>/')
@admin_login_req
def tag_list(page=1):
if page <= 0:
page = 1
page_data = Tag.query.order_by(
Tag.addtime.desc()
).paginate(page=page, per_page=10)
return render_template('admin/tag_list.html', page_data=page_data)
修改前端程式碼
<div class="box-body table-responsive no-padding">
{% for message in get_flashed_messages(category_filter=['info']) %}
<div class="alert alert-success alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
</button>
<h4><i class="icon fa fa-check"></i> 操作成功!</h4>
{{ message }}
</div>
{% endfor %}
<table class="table table-hover">
<tbody>
<tr>
<th>編號</th>
<th>名稱</th>
<th>新增時間</th>
<th>操作事項</th>
</tr>
{% for tag in page_data.items %}
<tr>
<td>{{ tag.id }}</td>
<td>{{ tag.name }}</td>
<td>{{ tag.addtime }}</td>
<td>
<a href="{{ url_for('admin.tag_edit', id=tag.id) }}" class="label label-success">編輯</a>
<a href="{{ url_for('admin.tag_del', id=tag.id) }}" class="label label-danger">刪除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
當標籤較多時,需要對標籤進行分頁
分頁http://www.pythondoc.com/flask-sqlalchemy/api.html?highlight=paginate#id4
新建一個分頁的macro
app/templates/ui/admin_page.html
{% macro pagination(data, url) -%}
{% if data %}
<ul class="pagination pagination-sm no-margin pull-right">
<li><a href="{{ url_for(url, page=1) }}">首頁</a></li>
{% if data.has_prev %}
<li><a href="{{ url_for(url, page=data.prev_num) }}">上一頁</a></li>
{% else %}
<li class="disabled"><a href="#">上一頁</a></li>
{% endif %}
{% for v in data.iter_pages() %}
{% if v == data.page %}
<li class="active"><a href="#">{{ v }}</a></li>
{% else %}
<li><a href="{{ url_for(url, page=v) }}">{{ v }}</a></li>
{% endif %}
{% endfor %}
{% if data.has_next %}
<li><a href="{{ url_for(url, page=data.next_num) }}">下一頁</a></li>
{% else %}
<li class="disabled"><a href="#">下一頁</a></li>
{% endif %}
<li><a href="{{ url_for(url, page=data.pages) }}">尾頁</a></li>
</ul>
{% endif %}
{%- endmacro %}
在標籤列表中使用這個macro
{% extends 'admin/admin.html' %}
{% from 'ui/admin_page.html' import pagination %}
...
<div class="box-footer clearfix">
{{ pagination(page_data, 'admin.tag_list') }}
</div>
刪除標籤
檢視函式
@admin.route('/tag/del/<int:id>/')
@admin_login_req
def tag_del(id=None):
tag = Tag.query.filter_by(id=id).first_or_404()
db.session.delete(tag)
db.session.commit()
flash('刪除標籤成功!', 'info')
return redirect(url_for('admin.tag_list', page=1))
修改標籤列表中刪除按鈕的a標籤
<a href="{{ url_for('admin.tag_del', id=tag.id) }}" class="label label-danger">刪除</a>
修改標籤
檢視函式
@admin.route('/tag/edit/<int:id>/', methods=['GET', 'POST'])
@admin_login_req
def tag_edit(id=None):
form = TagForm()
tag = Tag.query.get_or_404(id)
if form.validate_on_submit():
data = form.data
tag_count = Tag.query.filter_by(name=data['name']).count()
if tag.name != data['name'] and tag_count == 1:
flash("標籤已存在!", 'error')
return redirect(url_for('admin.tag_edit', id=id))
tag.name=data['name']
db.session.add(tag)
db.session.commit()
flash("標籤修改成功!", 'info')
return redirect(url_for('admin.tag_list', page=1))
return render_template('admin/tag_edit.html', form=form, tag=tag)
新建app/templates/admin/tag_edit.html
,用來進行標籤的修改
程式碼和新增標籤中的幾乎一致,只是需要顯示標籤的名字
<div class="form-group">
<label for="input_name">{{ form.name.label }}</label>
{{ form.name(value=tag.name) }}
{% for err in form.name.errors %}
<div class="col-md-12" id="input_user" style="color: red">{{ err }}</div>
{% endfor %}
</div>
電影管理
新建電影表單
app/admin/forms.py
class MovieForm(FlaskForm):
title = StringField(
label='片名',
validators=[
DataRequired("請輸入片名!")
],
description="片名",
render_kw={
"class": "form-control",
"id": "input_title",
"placeholder": "請輸入片名!"
}
)
url = FileField(
label='檔案',
validators=[
DataRequired("請上傳檔案!")
],
description="檔案",
)
info = TextAreaField(
label='簡介',
validators=[
DataRequired("請輸入簡介!")
],
description="簡介",
render_kw={
"class": "form-control",
"rows": "10",
"id": "input_info",
}
)
logo = FileField(
label='封面',
validators=[
DataRequired("請上傳封面!")
],
description="封面",
)
star = SelectField(
label='星級',
validators=[
DataRequired("請選擇星級!")
],
coerce=int,
choices=[(1, '1星'), (2, '2星'), (3, '3星'), (4, '4星'), (5, '5星')],
description="星級",
render_kw={
"class": "form-control",
}
)
tag_id = SelectField(
label='標籤',
validators=[
DataRequired("請選擇標籤!")
],
coerce=int,
choices=[(v.id, v.name) for v in tags],
description="標籤",
render_kw={
"class": "form-control",
}
)
area = StringField(
label='地區',
validators=[
DataRequired("請輸入地區!")
],
description="地區",
render_kw={
"class": "form-control",
"placeholder": "請輸入地區!"
}
)
length = StringField(
label='片長',
validators=[
DataRequired("請輸入片長!")
],
description="片長",
render_kw={
"class": "form-control",
"placeholder": "請輸入片長!"
}
)
release_time = StringField(
label='上映時間',
validators=[
DataRequired("請選擇上映時間!")
],
description="上映時間",
render_kw={
"class": "form-control",
"id": "input_release_time",
"placeholder": "請選擇上映時間!"
}
)
submit = SubmitField(
'編輯',
render_kw={
"class": "btn btn-primary"
}
)
def validate_title(self, field):
title = field.data
num = Movie.query.filter_by(title=title).count()
if num > 0:
raise ValidationError("該電影已存在!")
在app初始化檔案中定義上傳檔案的目錄
app.config['UP_DIR'] = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'static/uploads/')
新增電影
檢視函式
檔名稱檢測
from werkzeug.utils import secure_filename
def change_filename(filename):
fileinfo = os.path.splitext(filename)
filename = datetime.datetime.now().strftime('%Y%m%d%H%M%S') + str(uuid.uuid4().hex) + fileinfo[-1]
return filename
@admin.route('/movie/add/', methods=['GET', 'POST'])
@admin_login_req
def movie_add():
form = MovieForm()
if form.validate_on_submit():
data = form.data
file_url = secure_filename(form.url.data.filename)
file_logo = secure_filename(form.logo.data.filename)
if not os.path.exists(app.config['UP_DIR']):
os.makedirs(app.config['UP_DIR'])
os.chmod(app.config['UP_DIR'], 6)
url = change_filename(file_url)
logo = change_filename(file_logo)
form.url.data.save(app.config['UP_DIR'] + url)
form.logo.data.save(app.config['UP_DIR'] + logo)
movie = Movie(
title=data['title'],
url=url,
info=data['info'],
logo=logo,
star=int(data['star']),
playnum=0,
commentnum=0,
tag_id=int(data['tag_id']),
area=data['area'],
release_time=data['release_time'],
length=data['length']
)
db.session.add(movie)
db.session.commit()
flash('電影新增成功!', 'info')
return redirect(url_for('admin.movie_add'))
return render_template('admin/movie_add.html', form=form)
app/templates/admin/movie_add.html
{% extends 'admin/admin.html' %}
{% block content %}
<section class="content-header">
<h1>微電影管理系統</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> 電影管理</a></li>
<li class="active">新增電影</li>
</ol>
</section>
<section class="content" id="showcontent">
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">新增電影</h3>
</div>
<form role="form" method="post" enctype="multipart/form-data">
<div class="box-body">
{% for message in get_flashed_messages(category_filter=['info']) %}
<div class="alert alert-success alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
</button>
<h4><i class="icon fa fa-check"></i> 操作成功!</h4>
{{ message }}
</div>
{% endfor %}
<div class="form-group">
<label for="input_title">{{ form.title.label }}</label>
{{ form.title }}
{% for err in form.title.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_url">{{ form.url.label }}</label>
{{ form.url }}
{% for err in form.url.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
<div style="margin-top:5px;">
{# <div id="moviecontainer"></div>#}
</div>
</div>
<div class="form-group">
<label for="input_info">{{ form.info.label }}</label>
{{ form.info }}
{% for err in form.info.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_logo">{{ form.logo.label }}</label>
{{ form.logo }}
{% for err in form.logo.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
{# <img data-src="holder.js/262x166" style="margin-top:5px;" class="img-responsive"#}
{# alt="">#}
</div>
<div class="form-group">
<label for="input_star">{{ form.star.label }}</label>
{{ form.star }}
{% for err in form.star.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_tag_id">{{ form.tag_id.label }}</label>
{{ form.tag_id }}
{% for err in form.tag_id.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_area">{{ form.area.label }}</label>
{{ form.area }}
{% for err in form.area.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_length">{{ form.length.label }}</label>
{{ form.length }}
{% for err in form.length.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_release_time">{{ form.release_time.label }}</label>
{{ form.release_time }}
{% for err in form.release_time.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
</div>
<div class="box-footer">
{{ form.csrf_token }}
{{ form.submit }}
</div>
</form>
</div>
</div>
</div>
</section>
{% endblock %}
電影列表
檢視函式
@admin.route('/movie/list/<int:page>/')
@admin_login_req
def movie_list(page=1):
if page <= 0:
page = 1
page_data = Movie.query.join(Tag).filter(
Tag.id == Movie.tag_id
).order_by(
Movie.addtime.desc()
).paginate(page=page, per_page=10)
return render_template('admin/movie_list.html', page_data=page_data)
app/templates/admin/movie_list.html
{% extends 'admin/admin.html' %}
{% from 'ui/admin_page.html' import pagination %}
{% block content %}
<section class="content-header">
<h1>微電影管理系統</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> 電影管理</a></li>
<li class="active">電影列表</li>
</ol>
</section>
<section class="content" id="showcontent">
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">電影列表</h3>
<div class="box-tools">
<div class="input-group input-group-sm" style="width: 150px;">
<input type="text" name="table_search" class="form-control pull-right"
placeholder="請輸入關鍵字...">
<div class="input-group-btn">
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i>
</button>
</div>
</div>
</div>
</div>
<div class="box-body table-responsive no-padding">
{% for message in get_flashed_messages(category_filter=['info']) %}
<div class="alert alert-success alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
</button>
<h4><i class="icon fa fa-check"></i> 操作成功!</h4>
{{ message }}
</div>
{% endfor %}
<table class="table table-hover">
<tbody>
<tr>
<th>編號</th>
<th>片名</th>
<th>片長</th>
<th>標籤</th>
<th>地區</th>
<th>星級</th>
<th>播放數量</th>
<th>評論數量</th>
<th>上映時間</th>
<th>操作事項</th>
</tr>
{% for data in page_data.items %}
<tr>
<td>{{ data.id }}</td>
<td>{{ data.title }}</td>
<td>{{ data.length }}分鐘</td>
<td>{{ data.tag.name }}</td>
<td>{{ data.area }}</td>
<td>{{ data.star }}</td>
<td>{{ data.playnum }}</td>
<td>{{ data.commentnum }}</td>
<td>{{ data.release_time }}</td>
<td>
<a href="{{ url_for('admin.movie_edit', id=data.id) }}" class="label label-success">編輯</a>
<a href="{{ url_for('admin.movie_del', id=data.id) }}"
class="label label-danger">刪除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="box-footer clearfix">
{{ pagination(page_data, 'admin.movie_list') }}
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block js %}
<script>
$(document).ready(function () {
$('#g-3').addClass('active');
$('#g-3-2').addClass('active');
})
</script>
{% endblock %}
修改app/templates/admin/grid.html
<li id="g-3-2">
<a href="{{ url_for('admin.movie_list', page=1) }}">
<i class="fa fa-circle-o"></i> 電影列表
</a>
</li>
刪除電影
檢視函式
@admin.route('/movie/del/<int:id>/')
@admin_login_req
def movie_del(id=None):
movie = Movie.query.get_or_404(int(id))
db.session.delete(movie)
db.session.commit()
flash('電影刪除成功!', 'info')
return redirect(url_for('admin.movie_list', page=1))
修改一下前端刪除按鈕a標籤
修改電影
檢視函式
@admin.route('/movie/edit/<int:id>', methods=['GET', 'POST'])
@admin_login_req
def movie_edit(id=None):
form = MovieForm()
# # 如果不設定,預設依然會讓上傳檔案
# form.url.flags.required = False
# form.logo.flags.required = False
# # 取消校驗,可能沒有上傳檔案
# form.url.validators=[]
# form.logo.validators=[]
# 取消後如果沒有上傳檔案,form.url.data是一個str物件
# 上傳檔案後是才是一個檔案物件
# 為了方便,設定必須上傳檔案
movie=Movie.query.get_or_404(int(id))
if request.method =='GET':
form.info.data = movie.info
form.tag_id.data = movie.tag_id
form.star.data = movie.star
if form.validate_on_submit():
data = form.data
movie_count = Movie.query.filter_by(title=data['title']).count()
if movie_count == 1 and movie.title != data['title']:
flash('該電影已存在!', 'error')
return redirect(url_for('admin.movie_edit', id=id))
if not os.path.exists(app.config['UP_DIR']):
os.makedirs(app.config['UP_DIR'])
os.chmod(app.config['UP_DIR'], 6)
if form.url.data.filename !='':
file_url = secure_filename(form.url.data.filename)
movie.url = change_filename(file_url)
form.url.data.save(app.config['UP_DIR'] + movie.url)
if form.logo.data.filename != '':
file_logo = secure_filename(form.logo.data.filename)
movie.logo = change_filename(file_logo)
form.logo.data.save(app.config['UP_DIR'] + movie.logo)
movie.star = data['star']
movie.tag_id = data['tag_id']
movie.info = data['info']
movie.title = data['title']
movie.area = data['area']
movie.length = data['length']
movie.release_time = data['release_time']
db.session.add(movie)
db.session.commit()
flash('電影修改成功!', 'info')
return redirect(url_for('admin.movie_add', id=movie.id))
return render_template('admin/movie_edit.html', form=form, movie=movie)
app/templates/admin/movie_edit.html
{% extends 'admin/admin.html' %}
{% block content %}
<section class="content-header">
<h1>微電影管理系統</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> 電影管理</a></li>
<li class="active">修改電影</li>
</ol>
</section>
<section class="content" id="showcontent">
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">修改電影</h3>
</div>
<form role="form" method="post" enctype="multipart/form-data">
<div class="box-body">
{% for message in get_flashed_messages(category_filter=['info']) %}
<div class="alert alert-success alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
</button>
<h4><i class="icon fa fa-check"></i> 操作成功!</h4>
{{ message }}
</div>
{% endfor %}
{% for message in get_flashed_messages(category_filter=['error']) %}
<div class="alert alert-danger alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
</button>
<h4><i class="icon fa fa-ban"></i> 操作失敗!</h4>
{{ message }}
</div>
{% endfor %}
<div class="form-group">
<label for="input_title">{{ form.title.label }}</label>
{{ form.title(value=movie.title) }}
{% for err in form.title.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_url">{{ form.url.label }}</label>
{{ form.url }}
{% for err in form.url.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
<div style="margin-top:5px;">
<div id="moviecontainer"></div>
</div>
</div>
<div class="form-group">
<label for="input_info">{{ form.info.label }}</label>
{{ form.info }}
{% for err in form.info.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_logo">{{ form.logo.label }}</label>
{{ form.logo }}
{% for err in form.logo.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
<img src="{{ url_for('static', filename='uploads/'+movie.logo) }}" style="margin-top:5px;" class="img-responsive"
alt="">
</div>
<div class="form-group">
<label for="input_star">{{ form.star.label }}</label>
{{ form.star }}
{% for err in form.star.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_tag_id">{{ form.tag_id.label }}</label>
{{ form.tag_id }}
{% for err in form.tag_id.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_area">{{ form.area.label }}</label>
{{ form.area(value=movie.area) }}
{% for err in form.area.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_length">{{ form.length.label }}</label>
{{ form.length(value=movie.length) }}
{% for err in form.length.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_release_time">{{ form.release_time.label }}</label>
{{ form.release_time(value=movie.release_time) }}
{% for err in form.release_time.errors %}
<div class="col-md-12" style="color: red">{{ err }}</div>
{% endfor %}
</div>
</div>
<div class="box-footer">
{{ form.csrf_token }}
{{ form.submit }}
</div>
</form>
</div>
</div>
</div>
</section>
{% endblock %}
{% block js %}
<script src="{{ url_for('static',filename='jwplayer/jwplayer.js') }}"></script>
<script type="text/javascript">
jwplayer.key = "P9VTqT/X6TSP4gi/hy1wy23BivBhjdzVjMeOaQ==";
</script>
<script type="text/javascript">
jwplayer("moviecontainer").setup({
flashplayer: "{{ url_for('static',filename='jwplayer/jwplayer.flash.swf') }}",
playlist: [{
file: "{{ url_for('static', filename='uploads/'+movie.url) }}",
title: "{{ movie.title }}"
}],
modes: [{
type: "html5"
}, {
type: "flash",
src: "{{ url_for('static',filename='jwplayer/jwplayer.flash.swf') }}"
}, {
type: "download"
}],
skin: {
name: "vapor"
},
"playlist.position": "left",
"playlist.size": 200,
height: 250,
width: 387,
});
</script>
<script>
$(document).ready(function () {
$('#input_release_time').datepicker({
autoclose: true,
format: 'yyyy-mm-dd',
language: 'zh-CN',
});
});
</script>
<script>
$(document).ready(function () {
$('#g-3').addClass('active');
$('#g-3-1').addClass('active');
})
</script>
{% endblock %}
修改電影列表中的編輯按鈕
<a href="{{ url_for('admin.movie_edit', id=data.id) }}" class="label label-success">編輯</a>
預告管理
建立預告表單
class PreviewForm(FlaskForm):
title = StringField(
label='預告標題',
validators=[
DataRequired("請輸入預告標題!")
],
description="預告標題",
render_kw={
"class": "form-control",
"placeholder": "請輸入預告標題!"
}
)
logo = FileField(
label='預告封面',
validators=[
DataRequired("請上傳預告封面!"),
],
description="預告封面",
)
submit = SubmitField(
'編輯',
render_kw={
"class": "btn btn-primary"
}
)
def validate_title(self, field):
title = field.data
num = Preview.query.filter_by(title=title).count()
if num > 0:
raise ValidationError("該預告已存在!")
新增預告
檢視函式
@admin.route('/preview/add/', methods=['GET', 'POST'])
@admin_login_req
def preview_add():
form = PreviewForm()
if form.validate_on_submit():
data = form.data
file_logo = secure_filename(form.logo.data.filename)
if not os.path.exists(app.config['UP_DIR']):
os.makedirs(app.config['UP_DIR'])
os.chmod(app.config['UP_DIR'], 6)
logo = change_filename(file_logo)
form.logo.data.save(app.config['UP_DIR']+logo)
preview = Preview(
title=data['title'],
logo=logo
)
db.session.add(preview)
db.session.commit()
flash("預告新增成功!", 'info')
return redirect(url_for('admin.preview_add'))
return render_template('admin/preview_add.html', form = form)
app/templates/admin/preview_add.html
{% extends 'admin/admin.html' %}
{% block content %}
<section class="content-header">
<h1>微電影管理系統</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> 預告管理</a></li>
<li class="active">新增預告</li>
</ol>
</section>
<section class="content" id="showcontent">
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">新增預告</h3>
</div>
<form role="form" method="post" enctype="multipart/form-data">
<div class="box-body">
{% for message in get_flashed_messages(category_filter=['info']) %}
<div class="alert alert-success alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
</button>
<h4><i class="icon fa fa-check"></i> 操作成功!</h4>
{{ message }}
</div>
{% endfor %}
{% for message in get_flashed_messages(category_filter=['error']) %}
<div class="alert alert-danger alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
</button>
<h4><i class="icon fa fa-ban"></i> 操作失敗!</h4>
{{ message }}
</div>
{% endfor %}
<div class="form-group">
<label for="input_title">{{ form.title.label }}</label>
{{ form.title }}
{% for err in form.title.errors %}
<div class="col-md-12" id="input_user" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_logo">{{ form.logo.label }}</label>
{{ form.logo }}
{% for err in form.logo.errors %}
<div class="col-md-12" id="input_user" style="color: red">{{ err }}</div>
{% endfor %}
<img data-src="holder.js/700x320" style="margin-top:5px;" class="img-responsive"
alt="">
</div>
</div>
<div class="box-footer">
{{ form.csrf_token }}
{{ form.submit }}
</div>
</form>
</div>
</div>
</div>
</section>
{% endblock %}
{% block js %}
<script>
$(document).ready(function () {
$('#g-4').addClass('active');
$('#g-4-1').addClass('active');
})
</script>
{% endblock %}
預告列表
檢視函式
@admin.route('/preview/list/<int:page>/')
@admin_login_req
def preview_list(page=1):
if page <= 0:
page = 1
page_data = Preview.query.order_by(
Preview.addtime.desc()
).paginate(page=page, per_page=10)
return render_template('admin/preview_list.html', page_data=page_data)
app/templates/admin/preview_list.html
{% extends 'admin/admin.html' %}
{% from 'ui/admin_page.html' import pagination %}
{% block content %}
<section class="content-header">
<h1>微電影管理系統</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> 預告管理</a></li>
<li class="active">預告列表</li>
</ol>
</section>
<section class="content" id="showcontent">
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">預告列表</h3>
<div class="box-tools">
<div class="input-group input-group-sm" style="width: 150px;">
<input type="text" name="table_search" class="form-control pull-right"
placeholder="請輸入關鍵字...">
<div class="input-group-btn">
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i>
</button>
</div>
</div>
</div>
</div>
<div class="box-body table-responsive no-padding">
{% for message in get_flashed_messages(category_filter=['info']) %}
<div class="alert alert-success alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
</button>
<h4><i class="icon fa fa-check"></i> 操作成功!</h4>
{{ message }}
</div>
{% endfor %}
<table class="table table-hover">
<tbody>
<tr>
<th>編號</th>
<th>預告標題</th>
<th>預告封面</th>
<th>新增時間</th>
<th>操作事項</th>
</tr>
{% for data in page_data.items %}
<tr>
<td>{{ data.id }}</td>
<td>{{ data.title }}</td>
<td>
<img src="{{ url_for('static', filename='uploads/'+data.logo) }}"
class="img-responsive center-block" alt="" style="width: 140px">
</td>
<td>{{ data.addtime }}</td>
<td>
<a href="{{ url_for('admin.preview_edit', id=data.id) }}" class="label label-success">編輯</a>
<a href="{{ url_for('admin.preview_del', id=data.id) }}"
class="label label-danger">刪除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="box-footer clearfix">
{{ pagination(page_data, 'admin.preview_list') }}
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block js %}
<script>
$(document).ready(function () {
$('#g-4').addClass('active');
$('#g-4-2').addClass('active');
})
</script>
{% endblock %}
修改app/templates/admin/grid.html
<li id="g-4-2">
<a href="{{ url_for('admin.preview_list', page=1) }}">
<i class="fa fa-circle-o"></i> 預告列表
</a>
</li>
刪除預告
檢視函式
@admin.route('/preview/del/<int:id>/')
@admin_login_req
def preview_del(id=None):
preview = Preview.query.get_or_404(int(id))
db.session.delete(preview)
db.session.commit()
flash('預告刪除成功!', 'info')
return redirect(url_for('admin.preview_list', page=1))
修改預告
檢視函式
@admin.route('/preview/edit/<int:id>/', methods=['GET', 'POST'])
@admin_login_req
def preview_edit(id=None):
form = PreviewForm()
preview = Preview.query.get_or_404(int(id))
if request.method == 'GET':
form.title.data = preview.title
if form.validate_on_submit():
data = form.data
if not os.path.exists(app.config['UP_DIR']):
os.makedirs(app.config['UP_DIR'])
os.chmod(app.config['UP_DIR'], 6)
if form.logo.data.filename != '':
file_logo = secure_filename(form.logo.data.filename)
preview.logo = change_filename(file_logo)
form.logo.data.save(app.config['UP_DIR'] + preview.logo)
preview.title=data['title']
db.session.add(preview)
db.session.commit()
flash('預告修改成功!', 'info')
return redirect(url_for('admin.preview_edit', id=id))
return render_template('admin/preview_edit.html', form=form, preview=preview)
app/templates/admin/preview_edit.html
,程式碼拷貝新增預告,修改部分
<div class="form-group">
<label for="input_title">{{ form.title.label }}</label>
{{ form.title(value=preview.title) }}
{% for err in form.title.errors %}
<div class="col-md-12" id="input_user" style="color: red">{{ err }}</div>
{% endfor %}
</div>
<div class="form-group">
<label for="input_logo">{{ form.logo.label }}</label>
{{ form.logo }}
{% for err in form.logo.errors %}
<div class="col-md-12" id="input_user" style="color: red">{{ err }}</div>
{% endfor %}
<img src="{{ url_for('static', filename='uploads/'+preview.logo) }}" style="margin-top:5px;" class="img-responsive"
alt="">
</div>
會員管理
會員列表
@admin.route('/user/list/<int:page>/')
@admin_login_req
def user_list(page=1):
if page <= 0:
page = 1
page_data = User.query.order_by(
User.addtime.desc()
).paginate(page=page, per_page=10)
return render_template('admin/user_list.html', page_data=page_data)
app/templates/admin/user_list.html
{% extends 'admin/admin.html' %}
{% from 'ui/admin_page.html' import pagination %}
{% block content %}
<section class="content-header">
<h1>微電影管理系統</h1>
<ol class="breadcrumb">
<li><a href="#"><i class="fa fa-dashboard"></i> 會員管理</a></li>
<li class="active">會員列表</li>
</ol>
</section>
<section class="content" id="showcontent">
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header">
<h3 class="box-title">會員列表</h3>
<div class="box-tools">
<div class="input-group input-group-sm" style="width: 150px;">
<input type="text" name="table_search" class="form-control pull-right"
placeholder="請輸入關鍵字...">
<div class="input-group-btn">
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i>
</button>
</div>
</div>
</div>
</div>
<div class="box-body table-responsive no-padding">
{% for message in get_flashed_messages(category_filter=['info']) %}
<div class="alert alert-success alert-dismissible">
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×
</button>
<h4><i class="icon fa fa-check"></i> 操作成功!</h4>
{{ message }}
</div>
{% endfor %}
<table class="table table-hover">
<tbody>
<tr>
<th>編號</th>
<th>暱稱</th>
<th>郵箱</th>
<th>手機</th>
<th>頭像</th>
{# <th>狀態</th>#}
<th>註冊時間</th>
<th>操作事項</th>
</tr>
{% for data in page_data.items %}
<tr>
<td>{{ data.id }}</td>
<td>{{ data.name }}</td>
<td>{{ data.email }}</td>
<td>{{ data.phone }}</td>
<td>
<img src="{{ url_for('static', filename='uploads/users/'+data.face) }}"
style="width: 50px" class="img-responsive center-block" alt="">
</td>
{# <td>正常/凍結</td>#}
<td>{{ data.addtime }}</td>
<td>
<a class="label label-success"
href="{{ url_for('admin.user_view', id=data.id) }}">檢視</a>
{# #}
{# <a class="label label-info">解凍</a>#}
{# #}
{# <a class="label label-warning">凍結</a>#}
<a href="{{ url_for('admin.user_del', id=data.id) }}"
class="label label-danger">刪除</a>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="box-footer clearfix">
{{ pagination(page_data, 'admin.preview_list') }}
</div>
</div>
</div>
</div>
</section>
{% endblock %}
{% block js %}
<script>
$(document).ready(function () {
$('#g-5').addClass('active');
$('#g-5-1').addClass('active');
})
</script>
{% endblock %}
檢視會員
@admin.route('/user/view/<int:id>/')
@admin_login_req
def user_view(id=None):
user = User.query.get_or_404(int(id))
return render_template('admin/user_view.html', user=user)
<table class="table table-hover">
<tbody>
<tr>
<td class="td_bd">編號:</td>
<td>1</td>
</tr>
<tr>
<td class="td_bd">暱稱:</td>
<td>{{ user.name }}</td>
</tr>
<tr>
<td class="td_bd">郵箱:</td>
<td>{{ user.email }}</td>
</tr>
<tr>
<td class="td_bd">手機:</td>
<td>{{ user.phone }}</td>
</tr>
<tr>
<td class="td_bd">頭像:</td>
<td>
<img src="{{ url_for('static', filename='uploads/users/'+user.face) }}" style="width: 100px" class="img-responsive" alt="">
</td>
</tr>
<tr>
<td class="td_bd">註冊時間:</td>
<td>
{{ user.addtime }}
</td>
</tr>
<tr>
<td class="td_bd">唯一標誌符:</td>
<td>
{{ user.uuid }}
</td>
</tr>
<tr>
<td class="td_bd">個性簡介:</td>
<td>
{{ user.info }}
</td>
</tr>
</tbody>
</table>
刪除使用者
@admin.route('/user/del/<int:id>/')
@admin_login_req
def user_del(id=None):
user = User.query.get_or_404(int(id))
db.session.delete(user)
db.session.commit()
flash('會員刪除成功!', 'info')
return redirect(url_for('admin.user_list', page=1))
評論管理
評論列表
@admin.route('/comment/list/<int:page>/')
@admin_login_req
def comment_list(page=1):
if page <= 0:
page = 1
page_data = Comment.query.join(
Movie
).join(
User
).filter(
Movie.id==Comment.movie_id,
User.id ==Comment.user_id
).order_by(
Comment.addtime.desc()
).paginate(page=page, per_page=10)
return render_template('admin/comment_list.html', page_data=page_data)
<section class="content" id="showcontent">
<div class="row">
<div class="col-md-12">
<div class="box box-primary">
<div class="box-header with-border">
<h3 class="box-title">評論列表</h3>
<div class="box-tools">
<div class="input-group input-group-sm" style="width: 150px;">
<input type="text" name="table_search" class="form-control pull-right"
placeholder="請輸入關鍵字...">
<div class="input-group-btn">
<button type="submit" class="btn btn-default"><i class="fa fa-search"></i>
</button>
</div>
</div>
</div>
</div>