使用flask实现的一个简单的图片上传存储服务

Posted in 2017-3-14 6:16 | Category: Python | Tags: linux flask python

maple-file

使用flask实现的一个简单的图片上传服务

设计初衷

对于图片的存储,有很多选择,一般采用云服务如(七牛,又拍等),但是国内的服务像七牛 自定义域名竟然需要域名备案(Excuse me,当初就是因为备案麻烦才选择国外的),而且浪费了我十块钱,

而我又想像七牛一样可以直接在本地就可以上传图片,找来找去,没有找到一个比较合适的,所以花两天时间自己写了一个

使用

由于初衷是本地脚本就可以发布,所以没有前端界面,等以后有时间了再加上

API

  • /api/login
    • POST 登录
  • /api/logout
    • GET 注销
  • /api/albums
    • GET 获取相册列表
    • POST 新建相册
      • name 相册名称
      • description 相册描述
  • /api/albums/

    pk 相册ID

    • GET 获取相册信息
    • PUT 修改相册信息
      • name 相册名称
      • description 相册描述
    • DELETE 删除相册
    • /api/images
    • GET 获取图片列表
    • POST 上传图片
      • images 上传图片列表
      • album 相册ID(默认会新建一个default相册)
    • /api/images/
    • GET 获取图片信息
    • PUT 修改图片信息
      • name 图片名称
      • description 图片描述
    • DELETE 删除图片

配置

class Config(object):
    DEBUG = True # 生产环境设置为False
    SECRET_KEY = 'ccc' # import os;os.urandom(24)
    SECRET_KEY_SALT = 'ssss'
    JSON_AS_ASCII …

sqlalchemy序列化为json

Posted in 2016-12-13 7:53 | Category: Python | Tags: flask sqlalchemy python

为什么需要这个需求?

sqlalchemy 是个好东西,虽然其文档犹如老太婆的裹脚布--又臭又长,饱受诟病

使用 restful 时sqlalchemy返回的是一个 object 类,假设前后端分离,前端无法处理

如何实现?

直接给出代码

class Serializer(object):

    def __init__(self, instance, many=False, include=[], exclude=[], depth=2):
        self.instance = instance
        self.many = many
        self.include = include
        self.exclude = exclude
        self.depth = depth

    @property
    def data(self):
        if self.include and self.exclude:
            raise ValueError('include and exclude can\'t work together')
        if self.many:
            if isinstance(self.instance, list):
                return self._serializerlist(self.instance, self.depth)
            pageinfo = {
                'items': True,
                'pages': self.instance.pages,
                'has_prev': self.instance.has_prev,
                'page': self.instance.page,
                'has_next': self.instance.has_next,
                'iter_pages': list(self.instance.iter_pages(left_edge=1,
                                                            left_current …

flask使用token进行验证

Posted in 2016-12-13 3:32 | Category: Python | Tags: flask python token

为什么需要用token验证

原因呢是因为写博客时已经在本地写好了,但是要发表到网站上还需要这么几步:

  • [X] 打开浏览器
  • [X] 打开我的网站
  • [X] 进入登陆页
  • [X] 登陆
  • [X] 进入后台页
  • [X] 进入文章发表页
  • [X] 复制粘贴
  • [X] 发表

所以使用token验证成为必然

如何使用token?

生成token

使用itsdangerous对token进行加密

class User(model):
    ......

    @property
    def token(self):
        config = current_app.config
        secret_key = config.setdefault('SECRET_KEY')
        salt = config.setdefault('SECURITY_PASSWORD_SALT')
        serializer = URLSafeTimedSerializer(secret_key)
        # column = self.(需要加密的字段)
        token = serializer.dumps(column, salt=salt)
        return token

请保管好SECRET_KEYSECURITY_PASSWORD_SALT,不要泄露

验证token

class User(Model):
    ......

    @staticmethod
    def check_token(token, max_age=86400):
        config = current_app.config
        secret_key = config.setdefault('SECRET_KEY')
        salt = config.setdefault('SECURITY_PASSWORD_SALT')
        serializer = URLSafeTimedSerializer(secret_key)
        try:
            column = serializer.loads(token, salt=salt, max_age=max_age)
        except BadSignature:
            return False
        except SignatureExpired:
            return False
  • max-age 最大过期时间 …

如何安装及使用Honmaple社区程序

Posted in 2016-7-25 12:38 | Category: 生活随笔 | Tags: flask python

如何安装及使用

安装需要的package

pip install -r requirements.txt

配置config

查看配置详细介绍

注释下面代码

因为如果不注释的话 初始化数据库 会报错

文件位置: maple/topic/forms.py

category = SelectField(
    _('Category:'),
    choices=[(b.id, b.board + '   --' + b.parent_board)
             for b in Board.query.all()],
    coerce=int)

初始化数据库

python manage.py db init
python manage.py db migrate -m "first migrate"
python manage.py db upgrade

ok,将第三步中注释的内容恢复

创建管理员账户

python manager.py create_user

本地搭建


登陆并进入后台

python manager.py runserver

Visit http://forums.localhost:5000/admin

服务端搭建


参考 http://flask.pocoo.org/docs/0.11/deploying/

以我的配置为例:

配置nginx

server {
    listen …

flask时间格式化

Posted in 2016-6-15 23:46 | Category: Python | Tags: flask datetime python

在前端显示为该问题 "几分钟前发表或几天前发表"

后端通过filter注册

参考资料

设计需求

  • 如果问题发表超过 10天 ,则显示为 %Y-%m-%d %H:%M

  • 如果小于 10天 ,但是大于 1天 ,则显示为 n天前 发表

  • 如果小于 1天 ,但是大于 1小时 ,则显示为 n小时前 发表

  • 如果小于 1小时 ,但是大于 90秒 ,则显示为 n分钟前 发表

  • 如果小于 90秒 ,则显示为 刚刚 发表

具体实现

通过 diff.daysdiff.seconds 实现

比如,大于10天

if diff.days > 10:
    return dt.strftime('%Y-%m-%d %H:%M')

大于90秒,小于1小时

if diff.seconds <= 3600 and diff.seconds > 90:
    periods = ((diff.seconds / 60, "minute", "minutes"), )

具体代码

def timesince(dt, default="just now"):
    now = datetime.now()
    diff = now - dt
    if diff.days > 10:
        return dt.strftime('%Y-%m-%d %H:%M')
    if diff.days <= 10 and diff.days > 0:
        periods = ((diff.days, "day", "days"), )
    if diff.days <= 0 and diff.seconds > 3600:
        periods = ((diff.seconds / 3600, "hour …

基于restful的flask权限管理

Posted in 2016-6-10 1:34 | Category: Python | Tags: flask python restful

更新:2016-8-16

class RestBase(object):
    decorators = ()

    def __call__(self, func):
        f = self.method(func)
        if self.decorators:
            for dec in reversed(self.decorators):
                f = dec(f)
        return f

    def method(self, func):
        @wraps(func)
        def decorator(*args, **kwargs):
            meth = getattr(self, request.method.lower(), None)
            if request.method == 'HEAD':
                meth = getattr(self, 'get', None)
            if meth is not None:
                check = meth(*args, **kwargs)
                if check:
                    return self.callback()
            return func(*args, **kwargs)

        return decorator

    def callback(self):
        abort(403)

为什么需要restful形式的权限管理

最近在写flask应用时使用了 restful 形式的flask.views.MethodView,但是在对其进行权限管理时遇到了一些问题

flask文档上介绍说用

decorators = []

添加装饰器,但实际使用上,比如

  • getpost 采用不同的权限

get 不使用 login_required
post 需要 login_required

这样就不能使用 decorators 对视图进行装饰

  • post ,delete, put 都需要 login_required,但是get不需要 而 delete 又需要更高级别的权限,我们可以这样

    class AAA(MethodView):
    
        def get(self,uid):
            ...
    
        @login_required
        def post(self …

flask日志处理

Posted in 2016-5-24 22:36 | Category: Python | Tags: flask python logging

使用文档上的一句话:

Applications fail, servers fail. Sooner or later you will see an exception in production. Even if your code is 100% correct, you will still see exceptions from time to time. Why? Because everything else involved will fail.

应用发生错误时发送邮件

这里文档上个人认为说的不清不楚,毕竟想要使用还要看logging的文档

原文档

ADMINS = ['yourname@example.com']
if not app.debug:
    import logging
    from logging.handlers import SMTPHandler
    mail_handler = SMTPHandler('127.0.0.1',
                               'server-error@example.com',
                               ADMINS, 'YourApplication Failed')
    mail_handler.setLevel(logging.ERROR)
    app.logger.addHandler(mail_handler)

实际上这里的好多参数没有交代清楚,具体可以看https://docs.python.org/2/library/logging.handlers.html#smtp-handler

具体代码

import logging
from logging.handlers import SMTPHandler
from logging import Formatter
config = app.config
credentials = (config['MAIL_USERNAME'], config['MAIL_PASSWORD'])
mail_handler = SMTPHandler(
    secure=(),
    mailhost=(config['MAIL_SERVER'], config['MAIL_PORT']),
    fromaddr='',
    toaddrs='',
    subject='YourApplication Failed',
    credentials=credentials)

mail_handler.setFormatter(Formatter …

flask中生成atom

Posted in 2016-5-17 16:42 | Category: Python | Tags: flask python

参考资料1 参考资料2

下面是具体代码
# from urlparse import urljoin #python2
from urllib.parse import urljoin
from flask import request
from werkzeug.contrib.atom import AtomFeed


def make_external(url):
    return urljoin(request.url_root, url)


@site.route('/atom.xml')
def feed():
    feed = AtomFeed('Recent Articles',
                    feed_url=request.url,
                    url=request.url_root,
                    subtitle='I like solitude, yearning for freedom')
    articles = Articles.query.limit(15).all()
    for article in articles:
        feed.add(
            article.title,
            article.content,
            content_type='html',
            author=article.author,
            url=make_external(url_for('blog.view', id=article.id)),
            updated=article.publish,
            published=article.publish)
    return feed.get_response()

flask实现分页

Posted in 2016-4-25 1:7 | Category: Python | Tags: flask jinja2 python

数据库实现分页

offset

使用offset可以实现数据库分页功能

questions = Questions.query.offset(num) # 从num开始

limit

使用limit实现限制每页的文章数量

questions = Questions.query.offset(num).limit(6) #每页显示6篇文章

前端实现分页

需求设计

是使用上一页,下一页,用表单进行跳转,还是使用数字显示

获得文章数量 pages,和当前页码 number

简单的上一页,下一页

因为我的设计不是这个,所以简单说一下

下一页,上一页

jinja2 代码

<a href="{{ url_for('blog.index',number=number + 1)}}">下一页</a>
<a href="{{ url_for('blog.index',number=number - 1)}}">下一页</a>

首页没有上一页,尾页没有下一页

{% if number == pages %}
no next page
{% else %}
 <a href="{{ url_for('blog.index',number=number + 1)}}">下一页</a>
{% endif %}
{% if number == 1 %}
no previous page
{% else %}
 <a href="{{ url_for('blog.index',number=number - 1)}}">上一页</a>
{% endif %}

显示各个页码的分页

搞了一下午,终于搞定了

设计

总页码小于7,显示所有页码,总页码大于7,显示当前页码以及当前页码前两页和后两页. 当页码大于7时,当前页码小于4,显示1~6页的页码·····

使用

{{ import 'base/page.html' as page }}
number为当前页码,pages为总共的页码
{{ page.page('blog.index',number=number,pages=pages)}}
如果url_for需要加参数,使用
{{ page.page …

关于sqlalchemy的filter_by与filter

Posted in 2016-3-5 17:18 | Category: Python | Tags: flask sqlalchemy

都可看做是where但用法不一样

filter_by

question = Questions.query.filter_by(id=1).first()

filter

question = Questions.query.filter(Questions.id==1).first()

其实就是===还有是否带类名的区别
如果要select * from questions where id < 5
这时只能使用filter

questions = Questions.query.filter(Questions.id < 5).all()

多个条件

question = Questions.query.filter_by(name='hello',id=5).first()
# 或者
question = Questions.query.filter(Questions.name=='hello',Questions.id==5).first()

关于sqlalchemy的desc

Posted in 2016-3-5 17:2 | Category: Python | Tags: flask sqlalchemy

也就是降序排序

简单使用

questions = Questions.query.order_by(Questions.time.desc()).all()

设置默认排序

如果几乎所有的questions都是按照时间降序排序,总不能每一条语句都加上order_by(Questions.time.desc())
所以设置默认排序是有效的

class Questions(db.Model):
    __tablename__ = 'questions'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(50), nullable=False)
    content = db.Column(db.Text, nullable=False)
    time = db.Column(db.DateTime, nullable=False)

    __mapper_args__ = {
        "order_by": time.desc()
    }

如代码所示,使用__mapper_args__就可,__mapper_args__可以做很多事,具体看这里
设置默认排序下面的做法是错误

    __mapper_args__ = {
        "order_by": 'Questions.time.desc()'
    }
    # 或者
    __mapper_args__ = {
        "order_by": 'Questions.time desc'
    }
    # 或者
    __mapper_args__ = {
        "order_by": 'desc(Questions.time)'
    }

多对多默认排序

比如这样

question = Questions.query.filter_by(id=1).first()
for reply in question.replies:
    print(reply.content)

又如何设置回复是按照回复时间排序的
总不能这样

for reply in sorted(question.replies):
# 这会报错的
TypeError: unorderable types: Replies() < Replies()

怎么设置

replies = db.relationship('Questions',
                          backref=db.backref('replies',
                                             lazy='dynamic',
                                             order_by …

flask-sqlalchemy使用

Posted in 2016-3-5 0:40 | Category: Python | Tags: flask sqlalchemy

简单的例子这里已经有了
中文 这里记录一下平时我遇到的一些问题

一对多

需求:一个问题对应多个回复
下面给出代码(字段不完整)

class Questions(db.Model):
    __tablename__ = 'questions'
    id = db.Column(db.Integer, primary_key=True)
    title = db.Column(db.String(50), nullable=False)
    content = db.Column(db.Text, nullable=False)

    def __init__(self,title,content):
        self.title = title
        self.content = content

    def __repr__(self):
        return "<Questions %r>" % self.title


class Replies(db.Model):
    __tablename__ = 'replies'
    id = db.Column(db.Integer, primary_key=True)
    content = db.Column(db.Text, nullable=False)
    question_id = db.Column(db.Integer, db.ForeignKey('questions.id',
                                                      ondelete="CASCADE"))
    replies = db.relationship('Questions',
                              backref=db.backref('replies',
                                                 cascade='all,delete-orphan',
                                                 lazy='dynamic',
                                                 order_by='Replies.time')
                              )

    def __init__(self, content):
        self.content = content

    def __repr__(self):
        return "<Replies %r>" % self.content

会发现这样的两行(虽然实际上有好几行)

    question_id = db.Column(db.Integer, db.ForeignKey …

自定义jinja2转义标签

Posted in 2016-2-22 15:43 | Category: Python | Tags: flask jinja2 python

参考问题
参考资料
最终代码:

def safe_clean(text):
    from flask import Markup
    from bleach import clean
    tags = ['b','i','font','br']
    attrs = {
        '*':['style'],
        'font':['color']
    }
    styles = ['color']
    return Markup(clean(text,tags = tags,
                        attributes = attrs,
                        styles = styles))

自定义filter过滤器

关于flask-wtf中的validators

Posted in 2016-2-2 22:49 | Category: Python | Tags: wtforms flask

以前都是"吃快餐",利用别人的例子进行修改,弄得自己不明不白的,这次放假,静下心来好好的看看官方文档, 才发现写的代码有很多不必要的东西,可以进行很多优化

flask-wtforms是一个优秀的flask扩展,可惜官方文档说的不明不白,只是简单的说了大概的 功能,如果想要更好的利用这个扩展,请google wtforms,或者看这里

这里记录一下wtforms的validators
参考文档

class RegisterForm(Form):
    name = StringField('用户名:')
    email = StringField('邮箱:')
    passwd = PasswordField('密码:')
    repasswd = PasswordField('重复密码:')
    register = SubmitField('注册')

这只是一个最简单注册表单

慢慢地加上一些要求:

输入不能为空

from wtforms.validators import Required
name = StringField('用户名:',
                   [Required()])

限制name长度

from wtforms.validators import Length
name = StringField('用户名:',
                    [Length(min=4,
                           max=20)])

判断邮箱格式是否正确

from wtforms.validators import Email
email = StringField('邮箱',
                    [Email()])

比较两次密码是否一致

from wtforms.validators import Email
passwd = PasswordField('密码:', [EqualTo('repasswd')])
repasswd = PasswordField('重复密码:')

自定义错误信息

默认错误信息可以通过

form = RegisterForm()
print(form.errors) //或者print(form.name.errors)

查看
比如输入不能为空的默认错误信息

'This field is required.'

想要修改默认信息,请使用message
比如:

name = StringField('用户名:',
                   [Required(message=u'输入不能为空')])

同样的

Length(min=4,
       max=25,
       message=u'用户名长度在4到25个字符之间')
Email(message=u'错误的邮箱地址')
EqualTo('confirm' …

flask使用ajax

Posted in 2016-2-2 16:57 | Category: Python | Tags: ajax jquery flask

简单使用ajax

参考文档

<script type=text/javascript>
$(document).ready(function(){
    $('button#ajax').click(function() {
        $.ajax ({
            type : "POST",
            url : "{{ url_for('index.login') }}",
            data:JSON.stringify({
                name: $('input[name="name"]').val(),
                passwd: $('input[name="passwd"]').val()
            }),
            contentType: 'application/json;charset=UTF-8',
            success: function(result) {
                if (result.judge == true)
                {
                    window.location = '/';
                }
                else 
                {
                    $("#showerror").show();
                    $("#error").text(result.error);
                }
            }
        });
    });
});
</script>

ajax使用CSRF

参考文档

首先注册CSRF

from flask_wtf.csrf import CsrfProtect
csrf = CsrfProtect()
csrf.init_app(app)

在模板中使用

var csrftoken = "{{ csrf_token() }}"
    $.ajaxSetup({
        beforeSend: function(xhr, settings) {
            if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
                xhr.setRequestHeader("X-CSRFToken", csrftoken)
            }
        }
    });

flask应用部署——安装环境

Posted in 2016-1-6 0:58 | Category: Linux | Tags: nginx python3 linux flask

虽然以前安装过但是没有记录,这次重新安装,趁机记录下来,省得满世界地找

环境: centos7

安装nginx

参考资料
使用yum list nginx会发现nginx是1.6的版本,但现在nginx已经到了1.9,虽然不必那么新, 但是过旧的版本说不定会出现安全问题

# vim /etc/yum.repos.d/nginx.repo

输入

[nginx]
name=nginx repo
baseurl=http://nginx.org/packages/centos/$releasever/$basearch/
gpgcheck=0
enabled=1

然后

# yum list nginx  #你会发现nginx已经是1.8的版本
# yum install nginx
# systemctl start nginx #启动nginx

安装python3

centos7默认安装了python2.7的版本,但我习惯用python3,但是centos无法通过yum install python3安装, 只好自己编译了

安装必要的文件

# yum groupinstall "Development tools"
# yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel 
readline-devel tk-devel gdbm-devel db4-devel libpcap-devel xz-devel

下载python3源码

python3已到的python3.5,请按自己的需求下载

$ wget https://www.python.org/ftp/python/3.4.4/Python-3.4.4.tgz
$ tar xz Python-3.4.4.tgz
$ cd Python-3.4.4
$ ./configure
$ make
# make install

如果提示c 编译器未找到
yum install gcc gcc-c++

安装supervisor

编译安装python3的时候python3-pip就已经安装好了

# pip3 install supervisor
Collecting supervisor
  Downloading supervisor-3 …

我的flask项目结构

Posted in 2015-12-22 8:50 | Category: Python | Tags: flask python

听说每个flask应用最后都会写成自己的django,原来真的会是这样

总的结构

|── app/
│   ├── init.py
│   ├── email/
│   ├── forms/
│   ├── models/
│   ├── static/
│   ├── templates/
│   ├── utils/
│   └── views/
├── config/
│   ├── default.py
│   ├── development.py
│   └── init.py
├── gunicorn.conf
├── README.md
├── requirements.txt
├── run.py

视图

views/
├── admin.py
├── blog.py
├── book.py
├── index.py
├── init.py
└── question.py

表单

forms/
├── admin_form.py
├── article_form.py
├── comment_form.py
├── init.py
├── question_form.py
└── register_form.py

数据库

models/
├── account_db.py
├── article_db.py
├── base.py
├── book_db.py
├── init.py
└── question_db.py

模板

templates/
├── admin
│   ├── admin_article.html
│   ├── admin_base.html
│   ├── admin_comment.html
│   ├── admin_edit.html
│   ├── admin.html
│   ├── admin_post.html
│   ├── admin_question.html
│   └── admin_user.html
├── blog
│   ├── blog_archives.html
│   ├── blog_base.html
│   ├── blog_category.html
│   ├── blog.html
│   ├── blog_page.html
│   └── blog_tag.html
├── book
│   ├── book_base.html
│   ├── book.html
│   ├── book_intro.html
│   └── book_type.html
├── email.html
├── forget.html
├── index
│   ├── about_me.html
│   ├── base.html
│   ├── error.html
│   ├── forget.html
│   ├── index.html
│   ├── login.html …

flask学习——数据库操作

Posted in 2015-12-22 8:50 | Category: Python | Tags: sqlite sql flask python

flask 作为后端数据库操作是必要的,现在记录一下一些flask数据库的相关操作, 我将使用三种方法操作数据库
暂时使用较简单的sqlite作为例子

相关环境的安装

建议使用ve虚拟环境

sudo pacman -S sqlite # archlinux
sudo pip install virtualenv 
# 在vertualenv环境下执行
pip install Flask-SQLAlchemy Jinja2 SQLAlchemy

最好是多看文档

1.使用sqlite3模块API

参考资料
这是最简单的方法,不仅适用于flask,python的其他方面也一样适用,如爬虫之类

连接数据库

#!/usr/bin/env python
# -*- coding=UTF-8 -*-
import sqlite3
database = /path/test.db  #数据库文件路径
test = sqlite.connect('database') #连接数据库,如果数据库文件不存在则创建
print('connect database successfully')
test.close()  #关闭数据库连接

如果将数据库名改为:memory:,则在内存中打开数据库而不是磁盘

创建表

database = /path/test.db
test = sqlite.connect('database')
test.execute('''CREATE TABLE BOOKS
       (ID INT PRIMARY KEY     NOT NULL,
       TYPE           TEXT    NOT NULL,
       NAME           TEXT    NOT NULL,
       CONTENT        TEXT);''')
print("Table created successfully")
test.close()

插入数据

database = /path/test.db
test = sqlite.connect('database')
test.execute("INSERT INTO BOOKS (ID,TYPE,NAME,CONTENT) \
      VALUES (1, 'hello', 'world', 'helloworld')");
test.execute("INSERT INTO BOOKS (ID,TYPE,NAME,CONTENT …