Python版——博客网站<二>构建前端编写API

原创
2017/10/07 11:19
阅读数 214

开源地址:https://github.com/leebingbin/Python3.WebAPP.Blog

 

    至此,ORM框架、Web框架和配置都已就绪,我们可以开始编写一个最简单的MVC,把它们全部启动起来。

    通过Web框架的@get和ORM框架的Model支持,可以很容易地编写一个处理首页URL的函数:

 

    '__template__'指定的模板文件是test.html,其他参数是传递给模板的数据,所以我们在模板的根目录templates下创建test.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Test Users - Python3.WebAPP.BlogWebapp</title>
</head>
<body>
    <h1>All users</h1>
    {% for u in users %}
    <p>{{ u.name }} / {{ u.email }}</p>
    {% endfor %}
</body>
</html>

    接下来,可以用命令行启动Web服务器:

$ python3 app.py

    在浏览器中访问http://localhost:9000/。如果数据库的users表什么内容也没有,你就无法在浏览器中看到循环输出的内容。可以自己在MySQL的命令行里给users表添加几条记录,然后再访问,j就可以看到了:

 

    对于复杂的HTML前端页面来说,我们需要一套基础的CSS框架来完成页面布局和基本样式。另外,jQuery作为操作DOM的JavaScript库也必不可少。从零开始写CSS不如直接从一个已有的功能完善的CSS框架开始。有很多CSS框架可供选择。我们这次选择uikit这个强大的CSS框架。它具备完善的响应式布局,漂亮的UI,以及丰富的HTML组件,让我们能轻松设计出美观而简洁的页面。

    可以从uikit ( https://getuikit.com/ )首页下载打包的资源文件。

    所有的静态资源文件我们统一放到www/static目录下,并按照类别归类:

static/
+- css/
|  +- addons/
|  |  +- uikit.addons.min.css
|  |  +- uikit.almost-flat.addons.min.css
|  |  +- uikit.gradient.addons.min.css
|  +- python3.webapp.blog.css
|  +- uikit.almost-flat.min.css
|  +- uikit.gradient.min.css
|  +- uikit.min.css
+- fonts/
|  +- fontaBlogAPP-webfont.ttf
|  +- fontaBlogAPP-webfont.woff
|  +- FontBlogAPP.otf
|  +- fontBlogAPP-webfont.eot
+- img/
|  +- user.png
+- js/
|  +- jquery.min.js
|  +- python3.webapp.blog.js
|  +- sha1.min.js
|  +- sticky.min.js
|  +- uikit.min.js
|  +- uikit-icons.min.js
|  +- vue.min.js

    由于前端页面肯定不止首页一个页面,每个页面都有相同的页眉和页脚。如果每个页面都是独立的HTML模板,那么我们在修改页眉和页脚的时候,就需要把每个模板都改一遍,这显然是没有效率的。常见的模板引擎已经考虑到了页面上重复的HTML部分的复用问题。有的模板通过include把页面拆成三部分:

<html>
    <% include file="inc_header.html" %>
    <% include file="index_body.html" %>
    <% include file="inc_footer.html" %>
</html>

    这样,相同的部分inc_header.html和inc_footer.html就可以共享。

    但是include方法不利于页面整体结构的维护。jinjia2的模板还有另一种“继承”方式,实现模板的复用更简单。“继承”模板的方式是通过编写一个“父模板”,在父模板中定义一些可替换的block(块)。然后,编写多个“子模板”,每个子模板都可以只替换父模板定义的block。比如,定义一个最简单的父模板,这样,一旦定义好父模板的整体布局和CSS样式,编写子模板就会非常容易。

    让我们通过uikit这个CSS框架来完成父模板 __base__.html 的编写:

<!DOCTYPE html>
<!--
{% macro pagination(url, page) %}
    <ul class="uk-pagination">
        {% if page.has_previous %}
            <li><a href="{{ url }}{{ page.page_index - 1 }}"><i class="uk-icon-angle-double-left"></i></a></li>
        {% else %}
            <li class="uk-disabled"><span><i class="uk-icon-angle-double-left"></i></span></li>
        {% endif %}
            <li class="uk-active"><span>{{ page.page_index }}</span></li>
        {% if page.has_next %}
            <li><a href="{{ url }}{{ page.page_index + 1 }}"><i class="uk-icon-angle-double-right"></i></a></li>
        {% else %}
            <li class="uk-disabled"><span><i class="uk-icon-angle-double-right"></i></span></li>
        {% endif %}
    </ul>
{% endmacro %}
-->
<html>
<head>
    <meta charset="utf-8" />
    {% block meta %}<!-- block meta  -->{% endblock %}
    <title>{% block title %} ? {% endblock %} -  Python Blog Webapp</title>
    <link rel="stylesheet" href="/static/css/uikit.min.css">
    <link rel="stylesheet" href="/static/css/uikit.gradient.min.css">
    <link rel="stylesheet" href="/static/css/python3.webapp.blog.css"/>
    <script src="/static/js/jquery.min.js"></script>
    <script src="/static/js/sha1.min.js"></script>
    <script src="/static/js/uikit.min.js"></script>
    <script src="/static/js/sticky.min.js"></script>
    <script src="/static/js/vue.min.js"></script>
    <script src="/static/js/python3.webapp.blog.js"></script>
    {% block beforehead %}<!-- before head  -->{% endblock %}
</head>
<body>
    <nav class="uk-navbar uk-navbar-attached uk-margin-bottom">
        <div class="uk-container uk-container-center">
            <a href="/" class="uk-navbar-brand">Blog</a>
            <ul class="uk-navbar-nav">
                <li data-url="blogs"><a href="/"><i class="uk-icon-home"></i> 日志</a></li>
                <li><a target="_blank" href="https://my.oschina.net/u/3375733/blog?catalog=5663972&temp=1507368711950"><i class="uk-icon-book"></i> 教程</a></li>
                <li><a target="_blank" href="https://github.com/leebingbin/Python3.WebAPP.Blog"><i class="uk-icon-code"></i> 源码</a></li>
            </ul>
            <div class="uk-navbar-flip">
                <ul class="uk-navbar-nav">
                {% if __user__ %}
                    <li class="uk-parent" data-uk-dropdown>
                        <a href="#0"><i class="uk-icon-user"></i> {{ __user__.name }}</a>
                        <div class="uk-dropdown uk-dropdown-navbar">
                            <ul class="uk-nav uk-nav-navbar">
                                <li><a href="/signout"><i class="uk-icon-sign-out"></i> 登出</a></li>
                            </ul>
                        </div>
                    </li>
                {% else %}
                    <li><a href="/signin"><i class="uk-icon-sign-in"></i> 登陆</a></li>
                    <li><a href="/register"><i class="uk-icon-edit"></i> 注册</a></li>
                {% endif %}
                </ul>
            </div>
        </div>
    </nav>

    <div class="uk-container uk-container-center">
        <div class="uk-grid">
            <!-- content -->
            {% block content %}
            {% endblock %}
            <!-- // content -->
        </div>
    </div>

    <div class="uk-margin-large-top" style="background-color:#eee; border-top:1px solid #ccc;">
        <div class="uk-container uk-container-center uk-text-center">
            <div class="uk-panel uk-margin-top uk-margin-bottom">
                <p>
                    <a target="_blank" href="http://weibo.com/bingbinlee" class="uk-icon-button uk-icon-weibo"></a>
                    <a target="_blank" href="https://github.com/leebingbin" class="uk-icon-button uk-icon-github"></a>
                    <a target="_blank" href="http://www.linkedin.com/in/libingbin" class="uk-icon-button uk-icon-linkedin-square"></a>
                </p>
                <p>Powered by <a href="https://github.com/leebingbin/Python3.WebAPP.Blog">Python Blog Webapp</a>. Copyright &copy; 2016. [<a href="/manage/" target="_blank">Manage</a>]</p>
                <p><a href="https://my.oschina.net/u/3375733/blog" target="_blank">www.bingbinlee.com</a>. All rights reserved.</p>
                <a target="_blank" href="http://www.w3.org/TR/html5/"><i class="uk-icon-html5" style="font-size:64px; color: #444;"></i></a>
            </div>
        </div>
    </div>
</body>
</html>

    __base__.html定义的几个block作用如下:

    用于子页面定义一些meta,例如rss feed:

{% block meta %} ... {% endblock %}


    覆盖页面的标题:

{% block title %} ... {% endblock %}


    子页面可以在<head>标签关闭前插入JavaScript代码:

{% block beforehead %} ... {% endblock %}


    子页面的content布局和内容:

{% block content %}
    ...
{% endblock %}


    我们把首页改造一下,从 __base__.html 继承一个 blogs.html :

{% extends '__base__.html' %}

{% block title %}日志{% endblock %}

{% block beforehead %}

<script>
</script>

{% endblock %}

{% block content %}

    <div class="uk-width-medium-3-4">
    {% for blog in blogs %}
        <article class="uk-article">
            <h2><a href="/blog/{{ blog.id }}">{{ blog.name }}</a></h2>
            <p class="uk-article-meta">发表于{{ blog.created_at|datetime }}</p>
            <p>{{ blog.summary }}</p>
            <p><a href="/blog/{{ blog.id }}">继续阅读 <i class="uk-icon-angle-double-right"></i></a></p>
        </article>
        <hr class="uk-article-divider">
    {% endfor %}
    </div>

    <div class="uk-width-medium-1-4">
        <div class="uk-panel uk-panel-header">
            <h3 class="uk-panel-title">友情链接</h3>
            <ul class="uk-list uk-list-line">
                <li><i class="uk-icon-thumbs-o-up"></i> <a target="_blank" href="http://www.imooc.com/">编程</a></li>
                <li><i class="uk-icon-thumbs-o-up"></i> <a target="_blank" href="http://weread.qq.com/">读书</a></li>
                <li><i class="uk-icon-thumbs-o-up"></i> <a target="_blank" href="https://my.oschina.net/u/3375733/blog?catalog=5663972&temp=1507368711950">Python教程</a></li>
                <li><i class="uk-icon-thumbs-o-up"></i> <a target="_blank" href="https://github.com/">GitHub</a></li>
            </ul>
        </div>
    </div>

{% endblock %}

    接下来,和之前测试的test.html一样,也可以用命令行启动Web服务器。

    自从Roy Fielding博士在2000年他的博士论文中提出REST(Representational State Transfer)风格的软件架构模式后,REST就基本上迅速取代了复杂而笨重的SOAP,成为Web API的标准了。什么是Web API呢?

    如果我们想要获取一篇Blog,输入http://localhost:9000/blog/789,就可以看到id为789的Blog页面,但这个结果是HTML页面,它同时混合包含了Blog的数据和Blog的展示两个部分。对于用户来说,阅读起来没有问题,但是,如果机器读取,就很难从HTML中解析出Blog的数据。

    如果一个URL返回的不是HTML,而是机器能直接解析的数据,这个URL就可以看成是一个Web API。比如,读取http://localhost:9000/api/blogs/789,如果能直接返回Blog的数据,那么机器就可以直接读取。

    REST就是一种设计API的模式。最常用的数据格式是JSON。由于JSON能直接被JavaScript读取,所以,以JSON格式编写的REST风格的API具有简单、易读、易用的特点。

    编写API有什么好处呢?由于API就是把Web App的功能全部封装了,所以,通过API操作数据,可以极大地把前端和后端的代码隔离,使得后端代码易于测试,前端代码编写更简单。

    一个API也是一个URL的处理函数,我们希望能直接通过一个@api来把函数变成JSON格式的REST API,这样,获取注册用户可以用一个API实现如下:

@get('/api/users')
def api_get_users():
    users = yield from User.findAll(orderBy='created_at desc')
    for u in users:
        u.passwd = '******'
    return dict(users=users)

    只要返回一个dict,后续的response这个middleware就可以把结果序列化为JSON并返回。我们需要对Error进行处理,因此定义一个APIError,这种Error是指API调用时发生了逻辑错误(比如用户不存在),其他的Error视为Bug,返回的错误代码为internalerror。

    客户端调用API时,必须通过错误代码来区分API调用是否成功。错误代码是用来告诉调用者出错的原因。很多API用一个整数表示错误码,这种方式很难维护错误码,客户端拿到错误码还需要查表得知错误信息。更好的方式是用字符串表示错误代码,不需要看文档也能猜到错误原因。

    可以在浏览器直接测试API,例如,输入http://localhost:9000/api/users,就可以看到返回的JSON:

{
  "users":[
      {
        "name": "bingbinlee",
        "image": "http://www.gravatar.com/avatar/000000000000000000000000000000?dmm&size=120}",
        "admin": 1,
        "created_at": 1405684379.288,
        "password": "******",
        "email": "libingbin2015@aliyun.com",
        "id": "e807f1fcf82d132f9bb018ca6738a19f"
      }
    ]
}

 

本文为博主原创文章,转载请注明出处!

https://my.oschina.net/u/3375733/blog/

展开阅读全文
打赏
0
1 收藏
分享
加载中
更多评论
打赏
0 评论
1 收藏
0
分享
返回顶部
顶部