Flask+ Angularjs 实例: 创建博客
- 允许任何用户注册
- 允许注册的用户登录
- 允许登录的用户创建博客
- 允许在首页展示博客
- 允许登录的用户退
后端
- Flask-RESTful - Flask 的 RESTful 扩展
- Flask-SQLAlchemy - Flask 的 SQLAlchemy 扩展
- Flask-Bcrypt - Flask 的 一个为你的应用提供 bcrypt 哈希的工具扩展
- Flask-HTTPAuth - 一个为 Flask 路由提供 Basic and Digest HTTP authentication 的扩展
- Flask-WTF - http://docs.jinkan.org/docs/flask-wtf/
- WTForms-Alchemy - 一个 WTForms 扩展,能很简单的基于表单创建模型的工具集
- marshmallow - 是一个 ORM/ODM/ 的框架,用于转换复杂的数据类型,http://marshmallow.readthedocs.org/en/latest/quickstart.html
requirements.txt:
Flask==0.10.1
Flask-Bcrypt==0.6.0
Flask-HTTPAuth==2.2.1
Flask-RESTful==0.2.12
Flask-SQLAlchemy==1.0
Flask-WTF==0.10.0
Jinja2==2.7.3
MarkupSafe==0.23
SQLAlchemy==0.9.7
SQLAlchemy-Utils==0.26.9
WTForms==2.0.1
WTForms-Alchemy==0.12.8
WTForms-Components==0.9.5
Werkzeug==0.9.6
aniso8601==0.83
decorator==3.4.0
infinity==1.3
intervals==0.3.1
itsdangerous==0.24
marshmallow==0.7.0
py-bcrypt==0.4
pytz==2014.4
six==1.7.3
validators==0.6.0
wsgiref==0.1.2
应用的初始化设置
config.py:
DEBUG = True
WTF_CSRF_ENABLED = False
server.py:
basedir = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../') app = Flask(__name__)
app.config.from_object('app.config') # flask-sqlalchemy
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir, 'app.sqlite')
db = SQLAlchemy(app) # flask-restful
api = restful.Api(app) # flask-bcrypt
flask_bcrypt = Bcrypt(app) # flask-httpauth
auth = HTTPBasicAuth() @app.after_request
def after_request(response):
response.headers.add('Access-Control-Allow-Origin', '*')
response.headers.add('Access-Control-Allow-Headers', 'Content-Type,Authorization')
response.headers.add('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
return response import views
在这个文件中,我们初始化了 Flask,加载了从一个配置文件中加载了配置文件的变量,创建了 flask-sqlalchemy,flask-restful 对象等等。。。而且我们也在 after_request 函数中加了一些响应头,它允许跨域资源共享(CORS),这将允许我们的托管服务器(REST API)和客户端(AngularJS app)在不同的域以及不同的子域(例如:api.johnsblog.com 和 johnsblog.com)。在开发期间,这将允许我们把它们运行在不同的端口(例如:localhost:8000 和 localhost:5000)
Models
models.py:
class User(db.Model):
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(120), unique=True, nullable=False, info={'validators': Email()})
password = db.Column(db.String(80), nullable=False)
posts = db.relationship('Post', backref='user', lazy='dynamic') def __init__(self, email, password):
self.email = email
self.password = flask_bcrypt.generate_password_hash(password) def __repr__(self):
return '<User %r>' % self.email class Post(db.Model):
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(120), nullable=False)
body = db.Column(db.Text, nullable=False)
user_id = db.Column(db.Integer, db.ForeignKey('user.id'))
created_at = db.Column(db.DateTime, default=db.func.now()) def __init__(self, title, body):
self.title = title
self.body = body
self.user_id = g.user.id def __repr__(self):
return '<Post %r>' % self.title
在以上的代码中,我们定义了一个用户和文章的模型,用户模型有一个 id
作为它的主键,被定义成 integer 类型,email
和password
属性被定义成 strings。通过 posts
属性它也和 POST 模型有关联。POST 模型也有一个 id
作为它的主键,并也被定义成 integer,title
和 body
属性被定义成 strings。它也有一个 user_id
属性被定义成 integer 并作为 User 模型的 id 属性的外键。它还有一个 created_at
属性被定义成 DateTime。
Forms
现在我们已经完成了模型定义部分,让我们定义 forms,我们将使用 forms 校验我们的用户输入。为了 form 校验,我们将使用是一个名称为 WTForms 的 Flask 扩展,并且我们将使用 WTForms-Alchemy 扩展它 来更快更简单的定义我们的 forms。在 blog/server/app 目录下创建一个新文件并命名为 forms.py,然后拷贝和粘贴以下代码到文件中:
from flask.ext.wtf import Form from wtforms_alchemy import model_form_factory
from wtforms import StringField
from wtforms.validators import DataRequired from app.server import db
from models import User, Post BaseModelForm = model_form_factory(Form) class ModelForm(BaseModelForm):
@classmethod
def get_session(self):
return db.session class UserCreateForm(ModelForm):
class Meta:
model = User class SessionCreateForm(Form):
email = StringField('name', validators=[DataRequired()])
password = StringField('password', validators=[DataRequired()]) class PostCreateForm(ModelForm):
class Meta:
model = Post
Serializers:
为了在我们的 responses 中把我们的 model 实例渲染成 JSON,我们首先需要把它们转换成原生的 Python 数据类型, Flask-RESTful 可以使用 fields 模块 和 marshal_with() 装饰器(更多的详细信息请移步 - http://flask-restful.readthedocs.org/en/latest/quickstart.html#data-formatting)。当我开始构建 REST API 的时候我不知道 Flask-RESTful 支持这个,因此我以 Marshmallow 完成 http://marshmallow.readthedocs.org/en/latest/ 。
serializers.py:
from marshmallow import Serializer, fields class UserSerializer(Serializer):
class Meta:
fields = ("id", "email") class PostSerializer(Serializer):
user = fields.Nested(UserSerializer) class Meta:
fields = ("id", "title", "body", "user", "created_at")
Views
views.py:
from flask import g
from flask.ext import restful from server import api, db, flask_bcrypt, auth
from models import User, Post
from forms import UserCreateForm, SessionCreateForm, PostCreateForm
from serializers import UserSerializer, PostSerializer @auth.verify_password
def verify_password(email, password):
user = User.query.filter_by(email=email).first()
if not user:
return False
g.user = user
return flask_bcrypt.check_password_hash(user.password, password) class UserView(restful.Resource):
def post(self):
form = UserCreateForm()
if not form.validate_on_submit():
return form.errors, 422 user = User(form.email.data, form.password.data)
db.session.add(user)
db.session.commit()
return UserSerializer(user).data class SessionView(restful.Resource):
def post(self):
form = SessionCreateForm()
if not form.validate_on_submit():
return form.errors, 422 user = User.query.filter_by(email=form.email.data).first()
if user and flask_bcrypt.check_password_hash(user.password, form.password.data):
return UserSerializer(user).data, 201
return '', 401 class PostListView(restful.Resource):
def get(self):
posts = Post.query.all()
return PostSerializer(posts, many=True).data @auth.login_required
def post(self):
form = PostCreateForm()
if not form.validate_on_submit():
return form.errors, 422
post = Post(form.title.data, form.body.data)
db.session.add(post)
db.session.commit()
return PostSerializer(post).data, 201 class PostView(restful.Resource):
def get(self, id):
posts = Post.query.filter_by(id=id).first()
return PostSerializer(posts).data api.add_resource(UserView, '/api/v1/users')
api.add_resource(SessionView, '/api/v1/sessions')
api.add_resource(PostListView, '/api/v1/posts')
api.add_resource(PostView, '/api/v1/posts/<int:id>')
上面的 verify_password 函数被 auth.verify_password 装饰,并将被 Flask-HTTPAuth 使用来鉴定用户。它基本上通过 email 来获取用户,以及通过校验给出的密码是否与数据库中存储的密码匹配。
UserView 类将处理用户的注册请求,SessionView 类将处理用户的登录请求,PostListView 将处理获取文章列表和创建文章的请求。最后,PostView将处理获取单篇文章的请求。在文件的底部,我们简单的设置了 API 的资源路由。
创建数据库
db_create.py
:
from app.server import db db.create_all()
运行 REST API 服务
run.py:
from app.server import app app.run()
测试验证:
注册一个用户:
curl --dump-header - -H "Content-Type: application/json" -X POST -d '{"email": "johndoe@gmail.com","password": "admin"}' http://localhost:5000/api/v1/users
登陆一个用户
curl --dump-header - -H "Content-Type: application/json" -X POST -d '{"email": "johndoe@gmail.com","password": "admin"}' http://localhost:5000/api/v1/sessions
创建一篇文章:
curl --dump-header - -H "Content-Type: application/json" -H "Authorization: Basic am9obmRvZUBnbWFpbC5jb206YWRtaW4=" -X POST -d '{"title": "Example post","body": "Lorem ipsum"}' http://localhost:5000/api/v1/posts
(说明: 为了创建一篇文章,你需要发送一个 POST 请求给 localhost:5000/api/v1/posts。需要的属性是 title 和 body。因为创建文章的时候要求用户是已经登陆的,注意你需要发送一个包含 base64 编码的用户资格的 Authorization header ,它是通过冒号(":")分离的。)
获取文章
curl -i http://localhost:5000/api/v1/posts
前端:
bower.json
{
"name": "client",
"version": "0.0.0",
"authors": [
"John Kevin Basco <basco.johnkevin@gmail.com>"
],
"license": "MIT",
"ignore": [
"**/.*",
"node_modules",
"bower_components",
"test",
"tests"
],
"dependencies": {
"angular-route": "~1.2.21",
"bootstrap": "~3.2.0",
"restangular": "~1.4.0",
"angularjs": "~1.2.21",
"angular-local-storage": "~0.0.7"
}
}
index.html:
<!DOCTYPE html>
<html ng-app="Blog" ng-controller="ApplicationCtrl">
<head>
<meta charset="utf-8" />
<title>Blog</title> <link rel="stylesheet" type="text/css" href="./bower_components/bootstrap/dist/css/bootstrap.css">
<link rel="stylesheet" type="text/css" href="./css/theme.css">
<link rel="stylesheet" type="text/css" href="./css/styles.css">
</head>
<body> <div class="blog-masthead">
<div class="container">
<nav class="blog-nav">
<a class="blog-nav-item" href="#" ng-class="{active: isActive('/')}">Home</a>
<a class="blog-nav-item" href="#/sessions/create" ng-hide="isLoggedIn" ng-class="{active: isActive('/sessions/create')}">Login</a>
<a class="blog-nav-item" href="#/sessions/destroy" ng-show="isLoggedIn">Logout</a>
<a class="blog-nav-item" href="#/users/create" ng-class="{active: isActive('/users/create')}">Register</a>
<a class="blog-nav-item" href="#/posts/create" ng-show="isLoggedIn" ng-class="{active: isActive('/posts/create')}">Create a post</a>
</nav>
</div>
</div> <div class="container main-view"> <div ng-view></div> </div><!-- /.container --> <div class="blog-footer">
<p>Blog built by <a href="https://twitter.com/johnkevinmbasco">@johnkevinmbasco</a> using Flask and AngularJS.</p>
<p>
<a href="#">Back to top</a>
</p>
</div> <script type="text/javascript" src="./bower_components/angularjs/angular.js"></script>
<script type="text/javascript" src="./bower_components/angular-route/angular-route.js"></script>
<script type="text/javascript" src="./bower_components/lodash/dist/lodash.js"></script>
<script type="text/javascript" src="./bower_components/restangular/dist/restangular.js"></script>
<script type="text/javascript" src="./bower_components/angular-local-storage/angular-local-storage.js"></script>
<script type="text/javascript" src="./js/main.js"></script>
<script type="text/javascript" src="./js/controllers/HomeDetailCtrl.js"></script>
<script type="text/javascript" src="./js/controllers/ApplicationCtrl.js"></script>
<script type="text/javascript" src="./js/controllers/SessionCreateCtrl.js"></script>
<script type="text/javascript" src="./js/controllers/SessionDestroyCtrl.js"></script>
<script type="text/javascript" src="./js/controllers/UserCreateCtrl.js"></script>
<script type="text/javascript" src="./js/controllers/PostCreateCtrl.js"></script>
<script type="text/javascript" src="./js/factories/Session.js"></script>
<script type="text/javascript" src="./js/factories/User.js"></script>
<script type="text/javascript" src="./js/factories/Post.js"></script>
<script type="text/javascript" src="./js/services/AuthService.js"></script>
<script type="text/javascript" src="./js/directives/match.js"></script> </body>
</html>
.bowerrc:
{
"directory" : "bower_components"
}
angularjs脚本文件清单:
main.js:
window.Blog = angular.module('Blog', ['ngRoute', 'restangular', 'LocalStorageModule']) .run(function($location, Restangular, AuthService) {
Restangular.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
if (AuthService.isAuthenticated()) {
headers['Authorization'] = 'Basic ' + AuthService.getToken();
}
return {
headers: headers
};
}); Restangular.setErrorInterceptor(function(response, deferred, responseHandler) {
if (response.config.bypassErrorInterceptor) {
return true;
} else {
switch (response.status) {
case 401:
AuthService.logout();
$location.path('/sessions/create');
break;
default:
throw new Error('No handler for status code ' + response.status);
}
return false;
}
});
}) .config(function($routeProvider, RestangularProvider) { RestangularProvider.setBaseUrl('http://localhost:5000/api/v1'); var partialsDir = '../partials'; var redirectIfAuthenticated = function(route) {
return function($location, $q, AuthService) { var deferred = $q.defer(); if (AuthService.isAuthenticated()) {
deferred.reject()
$location.path(route);
} else {
deferred.resolve()
} return deferred.promise;
}
} var redirectIfNotAuthenticated = function(route) {
return function($location, $q, AuthService) { var deferred = $q.defer(); if (! AuthService.isAuthenticated()) {
deferred.reject()
$location.path(route);
} else {
deferred.resolve()
} return deferred.promise;
}
} $routeProvider
.when('/', {
controller: 'HomeDetailCtrl',
templateUrl: partialsDir + '/home/detail.html'
})
.when('/sessions/create', {
controller: 'SessionCreateCtrl',
templateUrl: partialsDir + '/session/create.html',
resolve: {
redirectIfAuthenticated: redirectIfAuthenticated('/posts/create')
}
})
.when('/sessions/destroy', {
controller: 'SessionDestroyCtrl',
templateUrl: partialsDir + '/session/destroy.html'
})
.when('/users/create', {
controller: 'UserCreateCtrl',
templateUrl: partialsDir + '/user/create.html'
})
.when('/posts/create', {
controller: 'PostCreateCtrl',
templateUrl: partialsDir + '/post/create.html',
resolve: {
redirectIfNotAuthenticated: redirectIfNotAuthenticated('/sessions/create')
}
});
})
所有代码下载地址:
Flask+ Angularjs 实例: 创建博客的更多相关文章
- 初试Nodejs——使用keystonejs创建博客网站2(修改模板)
上一篇(初试Nodejs——使用keystonejs创建博客网站1(安装keystonejs))讲了keystonejs的安装.安装完成后,已经具备了基本的功能,我们需要对页面进行初步修改,比如,增加 ...
- 全栈一路坑之使用django创建博客
最近在看一篇全栈增长工程师实战,然后学习里面的项目,结果发现作者用的技术太过老旧,好多东西都已经被抛弃了,所以结合着官方文档和自己的一些理解将错误的信息替换一下,边写边学习 准备工作和工具 作者说需要 ...
- 在 Windows Azure 网站上使用 Django、Python 和 MySQL:创建博客应用程序
编辑人员注释:本文章由 Windows Azure 网站团队的项目经理 Sunitha Muthukrishna 撰写. 根据您编写的应用程序,Windows Azure 网站上的基本Python 堆 ...
- 使用GitHub-Pages创建博客和图片上传问题解决
title: 使用GitHub Pages创建博客和图片上传问题解决 date: 2017-10-22 20:44:11 tags: IT 技术 toc: true 搭建博客 博客的搭建过程完全参照小 ...
- Django快速创建博客,包含了整个框架使用过程,简单易懂
创建工程 ...
- 使用hexo,创建博客
下载hexo工具 1 npm install hexo-cli -g 下载完成后可以在命令行下生成一个全局命令hexo搭建博客可用thinkjs 创建一个博客文件夹 1 hexo init 博客文件夹 ...
- 基于hexo创建博客(Github托管)
基于hexo的博客 搭建好的博客网站 dengshuo7412.com 搭建步骤 1.依赖文件下载 Node.js 2.Hexo的安装 3.部署到Github 4.Hexo创建博客基本操作 5.Hex ...
- 【Django实例】博客1
(上一篇) 一.概述 Blog是一个博客应用. dbe工程的目录结构,参考<序言>的最后部分.blog应用位于/home/russellluo/Django/dbe/dbe/blog目录下 ...
- 初试Nodejs——使用keystonejs创建博客网站1(安装keystonejs)
我正在阿里云上创建一个简单的个人博客网站,刚好正在尝试NodeJs,决定找一款基于NodeJs的CMS来完成这个工作,最后找到了KeyStoneJS. KeyStoneJS是基于Express和Mon ...
随机推荐
- C#入门笔记1
C#是用于创建要运行在.NET CLR上的应用程序的语言之一,从C和C++语言演化而来,是微软专门为使用.NET平台而创建的.优点:就是彻头彻尾为.NET Framework设计语言. C#能编写什么 ...
- mongodb Gridfs操作
GridFS 介绍 GridFS是MongoDB规范用于存储和检索大文件,如图片,音频文件,视频文件等.这是一种文件系统用来存储文件,但数据存储于MongoDB集合中.GridFS存储文件比其文档大小 ...
- CF1025C Plasticine zebra
思路: 不要被骗了,这个操作实际上tm是在循环移位. 实现: #include <bits/stdc++.h> using namespace std; int main() { stri ...
- VS2010每次编译都重新编译 解决方案
今天用VS2010的时候遇到这个问题,总搞不定,关掉重启各种尝试都木有用,最后突然发现项目的生成时间总是2009年...好吧,原来刚才笔记本死机了,我把笔记本拆了,拔下电池,擦了擦内存条,导致系统时间 ...
- freebsd安装ports
/etc/portsnap.conf 里面更改 SERVERNAME=portsnap.hshh.org portsnap的命令比较少 fetch 获取数据 extract 释放全部ports upd ...
- 超简单!一步创建自己的wifi热点~
还在用某某卫士.某某管家创建wifi热点,甚至被忽悠专门买一个随身wifi吗?现在答案明确了:你完全用不着它们了.因为有更简单的方法. 只需要两个bat文件.一个用来启动wifi热点,另一个用来关闭w ...
- 2018_oakland_linuxmalware
2018年oakland论文:理解linux恶意软件 论文地址:http://www.s3.eurecom.fr/~yanick/publications/2018_oakland_linuxmalw ...
- CSS BEM 命名规范简介
[前言] BEM 是一个简单又非常有用的命名约定.让你的前端代码更容易阅读和理解,更容易协作,更容易控制,更加健壮和明确,而且更加严密.这篇文章主要介绍了CSS BEM 命名规范简介(推荐)的相关资料 ...
- 工程化---cnpm不是内部命令的解决
(1)问题描述 安装完,执行cnpm -v发现报出不是内部命令. 安装成功如下图: (2)解决方案: 之前配置过默认安装都会在D:\\nodejs\node_global中,所有我们cd 到 这个路径 ...
- Codeforces Round #275(Div. 2)-C. Diverse Permutation
http://codeforces.com/contest/483/problem/C C. Diverse Permutation time limit per test 1 second memo ...