python操作mongodb数据库④mongodb新闻项目实战
参考文档:http://flask-mongoengine.readthedocs.io/en/latest/
目录: [root@node1 mongodb_version01]# tree -L 3 . ├── flask_mongo_news.py ├── forms.py ├── static │ ├── bootstrap-3.3.7-dist │ │ ├── css │ │ ├── fonts │ │ └── js │ ├── bootstrap-3.3.7-dist.zip │ ├── datatables.min.css │ ├── datatables.min.js │ ├── img │ │ └── news │ ├── index.css │ ├── jquery-3.3.1.min.js │ └── main.css └── templates ├── admin │ ├── add.html │ ├── admin_base.html │ ├── index.html │ └── update.html ├── cat.html ├── detail.html ├── home_base.html └── index.html
1.环境准备
①安装驱动程序flask-mongoengine
pip install flask-mongoengine
②向news集合中插入数据
db.news.insertMany( [ { "title":"朝鲜特种部队视频公布 展示士兵身体素质与意志", "img_url":"/static/img/news/01.png", "content":"在4月15日举行的朝鲜阅兵式上,除了各式展出的导弹,最亮眼的恐怕要数佩戴夜视仪的朝鲜特种部队了。4月19日,俄罗斯卫星网发布了截取自朝鲜官方电视台关于朝鲜特种部队士兵训练与展示的视频。在视频中,尽管训练科目并无太多新意,但是朝鲜士兵展示出了高度惊人的身体素质与顽强意志。", "is_valid": true, "news_type":"推荐" }, { "title":"男子长得像"祁同伟"挨打 打人者:为何加害检察官", "img_url":"/static/img/news/02.png", "content":"因与热门电视剧中人物长相相近,男子竟然招来一顿拳打脚踢。4月19日,打人男子周某被抓获。半个月前,酒后的周某看到KTV里有一名男子很像电视剧中的反派。二话不说,周某冲上去就问你为什么要加害检察官?男子莫名其妙,回了一句神经病。周某一听气不打一处来,对着男子就是一顿拳打脚踢,嘴里面还念叨着,“叫你加害检察官,我打死你!”随后,周某趁机逃走。受伤男子立即报警,周某被上海警方上网通缉。", "is_valid": true, "news_type":"百家" }, { "title":"导弹来袭怎么办?日本政府呼吁国民堕入地下通道", "img_url":"/static/img/news/03.JPEG", "content":"中新网4月21日电 据日媒报道,日本政府本月21日公布了弹道导弹可能落在国内时应采取的应对方法,呼吁民众身处室外时“尽可能躲入坚固的建筑物或地下通道”等", "is_valid": true, "news_type":"本地" }, { "title":"美监:朝在建能发射3发以上导弹的3000吨级新潜艇", "img_url":"/static/img/news/04.JPEG", "content":"【环球网报道】据韩联社4月21日报道,美国保守媒体《华盛顿自由灯塔》20日引用联合国报告报道称,朝鲜可能对“新浦”级潜艇进行改装,使其可连发多枚潜射导弹,韩国军方负责人21日对此表示,需进一步分析,持谨慎态度", "is_valid": true, "news_type":"推荐" }, { "title":"证监会:前发审委员冯小树违法买卖股票被罚4.99亿", "img_url":"/static/img/news/05.png", "content":"证监会新闻发言人张晓军21日表示,中央第七巡视组对证监会开展专项巡视期间,向证监会移交了前深交所工作人员、曾任股票发审委兼职委员冯小树涉嫌违法买卖股票的相关线索。会党委对相关线索高度重视,要求予以彻查。经过调查审理,通过对复杂商业架构的层层剖析,对繁复资金往来情况的抽丝剥茧,证监会查明,冯小树先后以岳母彭某嫦、配偶之妹何某梅名义入股拟上市公司,并在公司上市后抛售股票获利巨额利益,其交易金额累计达到2.51亿元,获利金额达2.48亿元", "is_valid": true, "news_type":"百家" }, { "title":"外交部回应安倍参拜靖国神社:同军国主义划清界限", "img_url":"/static/img/news/06.jpg", "content":"新闻图片", "is_valid": true, "news_type":"推荐" }, { "title":""萨德"供地违法?韩民众联名起诉要求撤回供地", "img_url":"/static/img/news/07.jpg", "content":"代理本案的“民主社会律师聚会”主张,韩国《国有财产特例限制法》第4条规定,未遵守该法附表中相关法案的国有财产特例无效,该法案附表中并不包括《驻韩美军地位协定》或有关履行《驻韩美军地位协定》的特别法案,因此,韩国政府供地是违反《国有财产特例限制法》向美军提供国有财产特例。", "is_valid": true, "news_type":"推荐" }, { "title":"金正恩:要由朝鲜民族自己谱写祖国统一新历史", "img_url":"/static/img/news/08.JPEG", "content":"3月5日,在朝鲜平壤,青瓦台国家安保室室长郑义溶(左)与朝鲜劳动党委员长金正恩握手。新华社平壤3月6日电 据朝中社6日报道,朝鲜最高领导人金正恩5日会见当天抵朝的韩国特使团,双方就北南首脑会晤交换意见并达成共识。报道说,金正恩在听取韩方特使转达的韩国总统文在寅有关南北首脑会晤的意愿后,与韩方交换意见并达成共识,他要求有关部门就此尽快采取相关实际举措。会见时,文在寅总统特使、青瓦台国家安保室长郑义溶向金正恩转交了文在寅的亲笔信。报道说,金正恩与韩方代表团就改善北南关系、保障朝鲜半岛和平稳定进行了开诚布公的交谈,还就缓和朝鲜半岛军事紧张状态、促进北南间多方面对话和接触、合作与交流交换了意见。金正恩说,要由朝鲜民族自己来齐心协力共同推动北南关系发展、谱写祖国统一的新历史,这是朝鲜一贯的原则立场,也是他本人坚定不移的意志。报道说,韩国特使团成员就金正恩向平昌冬奥会派遣高级别代表团等多个大规模代表团、帮助大会取得圆满成功表示感谢。金正恩说,作为血脉相连的同一民族,共同庆祝民族喜事并互相帮助,这次冬奥会是营造北南和解团结与对话良好气氛的重要契机。除郑义溶外,特使团其他成员韩国国家情报院院长徐薰、统一部次官千海成、国家情报院次长金相均和青瓦台国政状况室室长尹建永也参加了会见。朝鲜劳动党中央委员会副委员长金英哲和朝鲜劳动党中央委员会第一副部长金与正会见时在座。据朝中社报道,金正恩5日为韩国特使团成员举行了晚宴,金正恩的夫人李雪主,以及金英哲、金与正等参加晚宴。另据韩国媒体报道,韩国总统府青瓦台发言人金宜谦6日说,5日的会见和晚宴持续4个多小时。特使团将在结束后续会谈后,于6日下午返回首尔。青瓦台消息人士表示,这次会见成果“不令人失望”,韩朝就包括首脑会晤等事项达成一定程度的一致。", "is_valid": true, "news_type":"百家" } ] )
3.服务端代码flask_mongo_news.py
#coding:utf-8 from flask import Flask, render_template, redirect, flash, url_for from datetime import datetime from flask_mongoengine import MongoEngine from mongoengine import * from forms import NewsForm app = Flask(__name__) app.config['MONGODB_SETTINGS'] = { 'db': 'news', 'host': '127.0.0.1', 'port': 27017 } db = MongoEngine(app) app.config['SECRET_KEY'] = 'adfa@4314#31AD23#2' # 新闻类型 NEWS_TYPES = ( ('推荐', '推荐'), ('百家', '百家'), ('本地', '本地'), ('图片', '图片') ) class News(db.Document): ''' 新闻 ''' title = db.StringField(required = True, max_lenght = 64) content = db.StringField(required = True) news_type = db.StringField(required = True, choices = NEWS_TYPES) img_url = db.StringField() is_valid = db.BooleanField(default = True) created_at = db.DateTimeField(default = datetime.now()) updated_at = db.DateTimeField(default = datetime.now()) @app.route('/', methods = ['get']) def index(): ''' 首页 ''' # return 'ok' news_list = News.objects.filter(is_valid = True).all() return render_template('index.html', news_list = news_list) @app.route('/cat/<name>/', methods = ['GET', 'POST']) def cat(name): ''' 栏目 ''' news_list = News.objects.filter(is_valid = True, news_type = name).all() return render_template('cat.html', news_list = news_list) @app.route('/detail/<pk>/', methods = ['GET', 'POST']) def detail(pk): ''' 新闻详情页 ''' # 如果新闻不存在则报404错误 obj = News.objects.filter(pk = pk).first_or_404() return render_template('detail.html', obj = obj) @app.route('/admin/', methods = ['GET', 'POST']) @app.route('/admin/<page>/', methods = ['GET', 'POST']) def admin(page = None): ''' 后台首页 ''' # 如果page参数没有传就默认显示首页 if page == None: page = 1 page_data = News.objects.paginate(page=int(page), per_page=5) return render_template('admin/index.html', page_data = page_data, page = int(page)) @app.route('/admin/add/', methods = ['GET']) def admin_add(): ''' 添加新闻 ''' form = NewsForm() return render_template('admin/add.html', form = form) @app.route('/admin/do_add/', methods = ['GET','POST']) def admin_doadd(): ''' 添加新闻 ''' form = NewsForm() if form.validate_on_submit: # 获取数据 new_obj = News( title = form.title.data, content = form.content.data, news_type = form.news_type.data, img_url = form.img_url.data ) new_obj.save() flash('新闻添加成功') return redirect(url_for('admin')) return render_template('admin/add.html', form = form) @app.route('/admin/delete/<pk>/', methods = ['GET', 'POST']) def admin_delete(pk): ''' 删除新闻 ''' new_obj = News.objects.filter(pk=pk).first() print(new_obj.title) if not new_obj: return 'no' # 逻辑删除 new_obj.is_valid = False new_obj.save() return 'yes' # 物理删除 # new_obj.delete() # return 'yes' @app.route('/admin/update/<pk>/', methods=['POST', 'GET']) def admin_update(pk): ''' 修改新闻 ''' new_obj = News.objects.get_or_404(pk = pk) print(new_obj.title) form = NewsForm(obj = new_obj) if form.validate_on_submit(): new_obj.title = form.title.data new_obj.content = form.content.data new_obj.news_type = form.news_type.data new_obj.img_url = form.img_url.data new_obj.save() flash('新闻修改成功') return redirect(url_for('admin')) return render_template('admin/update.html', form = form) if __name__ == "__main__": app.run(debug = True)
表单元素forms.py
from flask_wtf import FlaskForm from wtforms import StringField, TextAreaField, SubmitField, SelectField from wtforms.validators import DataRequired class NewsForm(FlaskForm): """新闻表单数据验证""" title = StringField(label = '新闻标题', validators = [DataRequired('请输入标题')], description = '请输入标题', render_kw={'required':'required', 'class':'form-control'}) content = TextAreaField(label = '新闻内容', validators = [DataRequired('请输入新闻内容')], description = '请输入新闻内容', render_kw={'required':'required', 'class':'form-control'}) news_type = SelectField('新闻类型', choices = [('推荐','推荐'), ('百家', '百家'),('本地','本地'), ('图片','图片')]) img_url = StringField(label='新闻图片', description='请输入图片地址', render_kw={'required':'required', 'class':'form-control'}) submit = SubmitField('提交')
4.前端展示相关页面
模板home_base.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" href="{{ url_for('static', filename='bootstrap-3.3.7-dist/css/bootstrap.min.css')}}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='main.css')}}"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='datatables.min.css')}}"> <script type="text/javascript" src="{{ url_for('static', filename='jquery-3.3.1.min.js')}}"></script> <script type="text/javascript"> $(document).ready(function() { $('#example').DataTable(); } ); </script> {% block head %} <title>首页</title> {% endblock %} </head> <body> <div class="container"> <h1>新闻列表</h1> <nav class="navbar navbar-inverse"> <!-- 页面头部 --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar-menu" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> </div> <div id="navbar-menu" class="collapse navbar-collapse"> <ul class="nav navbar-nav"> <li class="active"><a href="/">首页</a></li> <li><a href="{{url_for('cat', name='推荐')}}">推荐</a></li> <li><a href="{{url_for('cat', name='百家')}}">百家</a></li> <li><a href="{{url_for('cat', name='本地')}}">本地</a></li> <li><a href="{{url_for('cat', name='图片')}}">图片</a></li> </ul> </div> </nav> <!-- 新闻内容部分 --> {% block content %} <!-- 内容区域 --> {% endblock %} </div> {% block extrajs %} <!-- 其他脚本 --> {% endblock %} </body> </html>
首页index.html
{% extends 'home_base.html' %} {% block content%} <div id="content" class="row-fluid"> <div class="col-md-12"> <table id="example" class="table table-striped table-bordered" cellspacing="0" width="100%"> <thead> <tr> <th>图片</th> <th>简介</th> </tr> </thead> <tbody> {% for obj in news_list %} <tr> <td> <img width=120 height=60 src="{{ obj.img_url }}" alt="图片"> </td> <td> <p> <a href="{{ url_for('detail', pk=obj.id) }}">{{ obj.title }}</a> <small>{{ obj.created_at }}</small> </p> </td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> {% endblock %} {% block extrajs %} <script type="text/javascript" src="{{ url_for('static', filename = 'datatables.min.js')}}"></script> {% endblock %}
栏目页cat.html
{% extends 'home_base.html' %} {% block head%} <title>{{ name }}</title> {% endblock %} {% block content%} <div id="content" class="row-fluid"> <div class="col-md-12"> <table id="example" class="table table-striped table-bordered" cellspacing="0" width="100%"> <thead> <tr> <th>图片</th> <th>简介</th> </tr> </thead> <tfoot> <tr> <th>Name</th> <th>Position</th> </tr> </tfoot> <tbody> {% for obj in news_list %} <tr> <td> <img width=120 height=60 src="{{ obj.img_url }}" alt="图片"> </td> <td> <p> <a href="{{ url_for('detail', pk=obj.id) }}">{{ obj.title }}</a> <small>{{ obj.created_at }}</small> </p> </td> </tr> {% endfor %} </tbody> </table> </div> </div> </div> {% endblock %} {% block extrajs %} <script type="text/javascript" src="{{ url_for('static', filename = 'datatables.min.js')}}"></script> {% endblock %}
新闻详情页detail.html
{% extends 'home_base.html' %} {% block head %} <title>新闻详情</title> {% endblock %} {% block content%} <div id="content" class="row-fluid"> <div class="col-md-9"> <h2>新闻详情,来自新闻id> {{obj.id}}</h2> </div> <div class="col-md-12"> <table id="example" class="table table-striped table-bordered" cellspacing="0" width="100%"> <thead> <tr> <th>{{ obj.content }}</th> </tr> </thead> <tbody> <tr> <td> <img width=600 height=500 src="{{ obj.img_url }}" alt="图片"> </td> <td> </tr> <tr> <td> <p> {{ obj.title }} <small>{{ obj.created_at }}</small> </p> </td> </tr> </tbody> </table> </div> </div> </div> {% endblock %} </body> </html>
5.后台相关页面
后台模板页面admin_base.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='bootstrap-3.3.7-dist/css/bootstrap.min.css')}}"> {% block head %} <title>首页</title> {% endblock %} </head> <body> <!-- 导航栏 --> <div class="container"> <div class="row"> <div class="bs-example" data-example-id="default-navbar"> <nav class="navbar navbar-default"> <div class="container-fluid"> <!-- Brand and toggle get grouped for better mobile display --> <div class="navbar-header"> <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="navbar-brand" href="{{ url_for('admin_add')}}">添加新闻</a> </div> <!-- Collect the nav links, forms, and other content for toggling --> <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1"> <ul class="nav navbar-nav"> <li class="active"><a href="#">Link <span class="sr-only">(current)</span></a></li> <li><a href="#">Link</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> <li role="separator" class="divider"></li> <li><a href="#">One more separated link</a></li> </ul> </li> </ul> <form class="navbar-form navbar-left"> <div class="form-group"> <input type="text" class="form-control" placeholder="Search"> </div> <button type="submit" class="btn btn-default">Submit</button> </form> <ul class="nav navbar-nav navbar-right"> <li><a href="#">Link</a></li> <li class="dropdown"> <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Dropdown <span class="caret"></span></a> <ul class="dropdown-menu"> <li><a href="#">Action</a></li> <li><a href="#">Another action</a></li> <li><a href="#">Something else here</a></li> <li role="separator" class="divider"></li> <li><a href="#">Separated link</a></li> </ul> </li> </ul> </div><!-- /.navbar-collapse --> </div><!-- /.container-fluid --> </nav> <!-- 新闻内容部分 --> {% block content %} <!-- 内容区域 --> {% endblock %} </div> {% block extrajs %} <!-- 其他脚本 --> {% endblock %} </body> </html>
后台首页admin/index.html
{% extends 'admin/admin_base.html' %} {% block head %} <title>新闻后台首页</title> {% endblock %} {% block content %} <!-- 消息闪现 --> {% for msg in get_flashed_messages() %} <p class="bg-success">{{msg}}</p> {% endfor %} <!-- 表格,存放新闻具体内容 --> <table class="table table-hover"> <tr class="info"> <th>编号</th> <th>新闻标题</th> <th>类别</th> <th>添加时间</th> <th>操作</th> </tr> {% for new_obj in page_data.items %} <tr class="active"> <td>{{ new_obj.id }}</td> <td>{{ new_obj.title }}</td> <td>{{new_obj.types }}</td> <td>{{new_obj.created_at }}</td> <td><a href="{{url_for('admin_update', pk = new_obj.id)}}" class='btn btn-success'>修改</a><a data-url="{{ url_for('admin_delete', pk=new_obj.id) }}" class='btn btn-danger'>删除</a></td> </tr> {% endfor %} </table> <!-- 分页,默认分页 --> <nav aria-label="Page navigation"> <ul class="pagination"> {% if page_data.has_prev %} <li> <a href="{{ url_for('admin', page=page_data.prev_num) }}" aria-label="Previous"> <span aria-hidden="true">«</span> </a> </li> {% else %} <li class="disabled"><a href="javascipt:;">»</a></li> {% endif %} {% for page in page_data.iter_pages() %} {% if page == page_data.page %} <li class="active"> <a href="javascript:;">{{ page }}</a> </li> {% else %} <li> <a href="{{ url_for('admin', page=page) }}">{{ page }}</a> </li> {% endif %} {% endfor %} {% if page_data.has_next %} <li> <a href="{{ url_for('admin', page=page_data.next_num) }}">»</a> </li> {% else %} <li class="disabled"> <a href="javascript:;">»</a> </li> {% endif %} </ul> </nav> </div> </div> {% endblock %} {% block extrajs %} <script type="text/javascript" src="{{ url_for('static', filename='jquery-3.3.1.min.js') }}"></script> <script type="text/javascript"> // 通过ajax异步删除新闻 $(function(){ $(".btn-danger").on('click', function(){ // alert('delete'); var _this = $(this); var url = _this.attr('data-url'); // 弹框确认是否要删除 if(confirm('确认删除吗?')){ // ajax异步请求 $.post(url, function(res){ // alert(res) if (res == 'yes'){ _this.parents('tr').hide() }else{ alert('删除失败'); } }) } }) }) </script> {% endblock %} </body> </html>
后台新增新闻页面admin/add.html
{% extends 'admin/admin_base.html' %} {% block head %} <title>新闻添加页面</title> {% endblock %} {% block content %} <!-- 添加新闻内容 --> <form action="/admin/do_add/" method="post"> <div class="form-group"> <label for="exampleInputEmail1">{{ form.title.label.text }}</label> <input type="text" name="title" class="form-control" id="exampleInputEmail1" placeholder="news title"> </div> <div class="form-group"> <label for="exampleInputPassword1">{{ form.news_type.label.text }}</label> <div> {{ form.news_type }} </div> </div> <div class="form-group"> <label for="exampleInputFile">{{ form.img_url.label.text }}</label> <input type="file" name="image" id="exampleInputFile"> <p class="help-block">新闻图片上传</p> </div> <div class="form-group"> {{ form.content }} </div> <br> {{ form.csrf_token }} {{ form.submit }} </form> {% endblock %} </body> </html>
后台新闻更新页面admin/update.html
{% extends 'admin/admin_base.html' %} {% block head %} <title>修改新闻</title> {% endblock %} {% block content %} <form role='form' class="form-horizontal" method="post"> <div class="form-group"> <label for="exampleInputEmail1">{{ form.title.label.text }}</label> <div> {{form.title}} </div> </div> <div class="form-group"> <label for="exampleInputPassword1">{{ form.news_type.label.text }}</label> <div> {{ form.news_type }} </div> </div> <div class="form-group"> <label for="exampleInputFile">{{ form.img_url.label.text }}</label> <input type="file" name="image" id="exampleInputFile"> {{ form.img_url }} <!-- <p class="help-block">新闻图片上传</p> --> </div> <div class="form-group"> {{ form.content }} </div> <br> {{ form.csrf_token }} {{ form.submit }} </form> {% endblock %} </body> </html>
注意:
Flask同一个方法处理get和Post请求,注意form.validate_on_submit()有括号