资讯专栏INFORMATION COLUMN

使用 Flask 和 AngularJS 构建博客 - 2

awokezhou / 2041人阅读

摘要:我们将使用方法创建一个。我们传递一个布尔类型,这个就是我们早先讨论的的参数。再使用和构建博客教程系列的第三部分见。所有的源码都在上,但是应用程序的源码还没有放上去,因为我们还没有完成它,等到第三部分写完以后再放全部的源码到上。

</>复制代码

  1. 注:该文作者是 John Kevin M. Basco,原文地址是 Building a blog using Flask and AngularJS Part 2

</>复制代码

  1. 注:翻译的第一部分请移步到 - 使用 Flask 和 AngularJS 构建博客 - 1

这是这个教程系列的第二部分,如果你还没有都第一部分,请移步到这里:http://blog.john.mayonvolcanosoftware.com/building-a-blog-using-flask-and-angularjs-part-1/

因为我们在该系列的第一部分已经构建好了 REST API ,在这部分我们将专注于构建一个 AngularJS 应用,用来使用我们构建的 REST API。

目录结构

AngularJS 应用的目录结构看起来像这样:

安装必要的包

我们将使用的包如下:

angular-route

bootstrap

restangular

angularjs

angular-local-storage

如果你不熟悉 Restangular 的话,我建议你首先读下 Restangular 的文档 - https://github.com/mgonto/restangular

或者如果你懒得读它的文档,随时继续阅读本教程,当你遇到如下对你没有意义的 Restangular 代码的时候,你可以参考下 Restangular 文档。

为了使得事情更容易,在 blog/client/ 目录创建一个 bower.json 文件,并且拷贝和粘贴以下内容:

</>复制代码

  1. {
  2. "name": "client",
  3. "version": "0.0.0",
  4. "authors": [
  5. "John Kevin Basco "
  6. ],
  7. "license": "MIT",
  8. "ignore": [
  9. "**/.*",
  10. "node_modules",
  11. "bower_components",
  12. "test",
  13. "tests"
  14. ],
  15. "dependencies": {
  16. "angular-route": "~1.2.21",
  17. "bootstrap": "~3.2.0",
  18. "restangular": "~1.4.0",
  19. "angularjs": "~1.2.21",
  20. "angular-local-storage": "~0.0.7"
  21. }
  22. }

然后进入 blog/client 目录,运行 bower install 来安装必要的包。

初始化应用设置

首先,在 blog/client 目录下创建一个新文件,并命名为 index.html,然后拷贝和粘贴以下内容:

</>复制代码

  1. Blog
  2. Home
  3. Login
  4. Logout
  5. Register
  6. Create a post
  7. Blog built by @johnkevinmbasco using Flask and AngularJS.

  8. Back to top

在以上代码中,我们只是简单的包含了我们稍后将要创建的 css 和 javascript 文件。我们也为 navigation bar, footer 等等添加了 html markup。你将注意到在以上的 gist 中,它包含了

这个 html 元素。这是一个当前的路由将用来插入模板的地方。你将看到这些事情:ng-class="{active: isActive("/")}"ng-show="isLoggedIn",忽略它们现在。我们将在 为 ApplicationCtrl 写代码的时候讨论它们。

添加一些样式

现在让我们添加一些样式以便这个 app 看起来更好。为了使我们的工作更轻松,让我们使用一个 Bootstrap 提供的主题 - http://getbootstrap.com/examples/blog/。在 app/client/css 目录下创建一个新文件,并命名为 theme.css,然后拷贝和粘贴以下内容:

</>复制代码

  1. /*
  2. * Globals
  3. */
  4. body {
  5. font-family: Georgia, "Times New Roman", Times, serif;
  6. color: #555;
  7. }
  8. h1, .h1,
  9. h2, .h2,
  10. h3, .h3,
  11. h4, .h4,
  12. h5, .h5,
  13. h6, .h6 {
  14. margin-top: 0;
  15. font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
  16. font-weight: normal;
  17. color: #333;
  18. }
  19. /*
  20. * Override Bootstrap"s default container.
  21. */
  22. @media (min-width: 1200px) {
  23. .container {
  24. width: 970px;
  25. }
  26. }
  27. /*
  28. * Masthead for nav
  29. */
  30. .blog-masthead {
  31. background-color: #428bca;
  32. -webkit-box-shadow: inset 0 -2px 5px rgba(0,0,0,.1);
  33. box-shadow: inset 0 -2px 5px rgba(0,0,0,.1);
  34. }
  35. /* Nav links */
  36. .blog-nav-item {
  37. position: relative;
  38. display: inline-block;
  39. padding: 10px;
  40. font-weight: 500;
  41. color: #cffffdeb;
  42. }
  43. .blog-nav-item:hover,
  44. .blog-nav-item:focus {
  45. color: #fff;
  46. text-decoration: none;
  47. }
  48. /* Active state gets a caret at the bottom */
  49. .blog-nav .active {
  50. color: #fff;
  51. }
  52. .blog-nav .active:after {
  53. position: absolute;
  54. bottom: 0;
  55. left: 50%;
  56. width: 0;
  57. height: 0;
  58. margin-left: -5px;
  59. vertical-align: middle;
  60. content: " ";
  61. border-right: 5px solid transparent;
  62. border-bottom: 5px solid;
  63. border-left: 5px solid transparent;
  64. }
  65. /*
  66. * Blog name and description
  67. */
  68. .blog-header {
  69. padding-top: 20px;
  70. padding-bottom: 20px;
  71. }
  72. .blog-title {
  73. margin-top: 30px;
  74. margin-bottom: 0;
  75. font-size: 60px;
  76. font-weight: normal;
  77. }
  78. .blog-description {
  79. font-size: 20px;
  80. color: #999;
  81. }
  82. /*
  83. * Main column and sidebar layout
  84. */
  85. .blog-main {
  86. font-size: 18px;
  87. line-height: 1.5;
  88. }
  89. /* Sidebar modules for boxing content */
  90. .sidebar-module {
  91. padding: 15px;
  92. margin: 0 -15px 15px;
  93. }
  94. .sidebar-module-inset {
  95. padding: 15px;
  96. background-color: #f5f5f5;
  97. border-radius: 4px;
  98. }
  99. .sidebar-module-inset p:last-child,
  100. .sidebar-module-inset ul:last-child,
  101. .sidebar-module-inset ol:last-child {
  102. margin-bottom: 0;
  103. }
  104. /* Pagination */
  105. .pager {
  106. margin-bottom: 60px;
  107. text-align: left;
  108. }
  109. .pager > li > a {
  110. width: 140px;
  111. padding: 10px 20px;
  112. text-align: center;
  113. border-radius: 30px;
  114. }
  115. /*
  116. * Blog posts
  117. */
  118. .blog-post {
  119. margin-bottom: 60px;
  120. }
  121. .blog-post-title {
  122. margin-bottom: 5px;
  123. font-size: 40px;
  124. }
  125. .blog-post-meta {
  126. margin-bottom: 20px;
  127. color: #999;
  128. }
  129. /*
  130. * Footer
  131. */
  132. .blog-footer {
  133. padding: 40px 0;
  134. color: #999;
  135. text-align: center;
  136. background-color: #f9f9f9;
  137. border-top: 1px solid #e5e5e5;
  138. }
  139. .blog-footer p:last-child {
  140. margin-bottom: 0;
  141. }

在 app/client/css 目录下创建另外一个文件,命名为 styles.css ,然后拷贝和粘贴一下内容:

</>复制代码

  1. .main-view {
  2. margin-top: 20px;
  3. margin-bottom: 20px;
  4. }

现在进入 blog/client 目录,然后运行这个命令:python -m SimpleHTTPServer 8000,现在访问 http://localhost:8000/,你应该可以看到应用已经有设计样式。

创建必须的 javascript 文件

注意:以下应用使用的配置变量的值,为了假话,被硬编码了。在生产环境的应用中,需要把它放入一个配置文件中,因此你可以根据不同的环境使用不同的值,比如开发环境和生产环境。

现在让我们创建必须的 javascript 文件,在 blog/client/js 目录下创建一个新文件,命名为 main.js 并且以下的代码拷贝和粘贴进文件:

</>复制代码

  1. window.Blog = angular.module("Blog", ["ngRoute", "restangular", "LocalStorageModule"])
  2. .run(function($location, Restangular, AuthService) {
  3. Restangular.setFullRequestInterceptor(function(element, operation, route, url, headers, params, httpConfig) {
  4. headers["Authorization"] = "Basic " + AuthService.getToken();
  5. return {
  6. headers: headers
  7. };
  8. });
  9. Restangular.setErrorInterceptor(function(response, deferred, responseHandler) {
  10. if (response.config.bypassErrorInterceptor) {
  11. return true;
  12. } else {
  13. switch (response.status) {
  14. case 401:
  15. AuthService.logout();
  16. $location.path("/sessions/create");
  17. break;
  18. default:
  19. throw new Error("No handler for status code " + response.status);
  20. }
  21. return false;
  22. }
  23. });
  24. })
  25. .config(function($routeProvider, RestangularProvider) {
  26. RestangularProvider.setBaseUrl("http://localhost:5000/api/v1");
  27. var partialsDir = "../partials";
  28. var redirectIfAuthenticated = function(route) {
  29. return function($location, $q, AuthService) {
  30. var deferred = $q.defer();
  31. if (AuthService.isAuthenticated()) {
  32. deferred.reject()
  33. $location.path(route);
  34. } else {
  35. deferred.resolve()
  36. }
  37. return deferred.promise;
  38. }
  39. }
  40. var redirectIfNotAuthenticated = function(route) {
  41. return function($location, $q, AuthService) {
  42. var deferred = $q.defer();
  43. if (! AuthService.isAuthenticated()) {
  44. deferred.reject()
  45. $location.path(route);
  46. } else {
  47. deferred.resolve()
  48. }
  49. return deferred.promise;
  50. }
  51. }
  52. $routeProvider
  53. .when("/", {
  54. controller: "HomeDetailCtrl",
  55. templateUrl: partialsDir + "/home/detail.html"
  56. })
  57. .when("/sessions/create", {
  58. controller: "SessionCreateCtrl",
  59. templateUrl: partialsDir + "/session/create.html",
  60. resolve: {
  61. redirectIfAuthenticated: redirectIfAuthenticated("/posts/create")
  62. }
  63. })
  64. .when("/sessions/destroy", {
  65. controller: "SessionDestroyCtrl",
  66. templateUrl: partialsDir + "/session/destroy.html"
  67. })
  68. .when("/users/create", {
  69. controller: "UserCreateCtrl",
  70. templateUrl: partialsDir + "/user/create.html"
  71. })
  72. .when("/posts/create", {
  73. controller: "PostCreateCtrl",
  74. templateUrl: partialsDir + "/post/create.html",
  75. resolve: {
  76. redirectIfNotAuthenticated: redirectIfNotAuthenticated("/sessions/create")
  77. }
  78. });
  79. })

在以上代码中,我们初始化了我们的应用,配置 Restangular 来包括每个请求的登录用户的用户名和密码,配置 Restangular 来使用一个错误拦截器,它将删除本地存储的所有已经保存的数据,并且如果 REST API 返回一个 401 状态码,则把用户跳转到登录页面。

在配置部分,我们设置了 Restangular 将使用的基准 url,我们也设置了路由。你将注意到 /posts/create 路由有一个解决属性包含了 redirectIfNotAuthenticated promise。是的,像你猜的那样,如果用户未被授权,它将把用户跳转到登录页面。/sessions/create 路由也有一个解决属性,但是它包含了 redirectIfAuthenticated promise 而不是 redirectIfNotAuthenticated promise。如果用户被授权了,它将简单的跳转用户到创建博客的页面。你可以阅读关于这个主题的更多信息 - http://blog.john.mayonvolcanosoftware.com/protecting-routes-in-angularjs/。

Factories

让我们创建我们的应用将使用到的工厂方法。

Post

让我们在 blog/client/js/factories 目录下创建一个新文件,命名为 Post.js ,然后拷贝并粘贴一下代码到文件中:

</>复制代码

  1. Blog.factory("Post", function(Restangular) {
  2. var Post;
  3. Post = {
  4. get: function() {
  5. return Restangular
  6. .one("posts")
  7. .getList();
  8. },
  9. create: function(data) {
  10. return Restangular
  11. .one("posts")
  12. .customPOST(data);
  13. }
  14. };
  15. return Post;
  16. })

在以上代码中,factory 将创建一个对象,该对象有一个 get 和 create 方法。我们将使用 get 方法来获取博客列表,create 方法来创建一篇博客。

Session

让我们在 blog/client/js/factories 目录下创建一个新文件,命名为 Session.js ,然后拷贝并粘贴一下代码到文件中:

</>复制代码

  1. Blog.factory("Session", function(Restangular) {
  2. var Session;
  3. Session = {
  4. create: function(data, bypassErrorInterceptor) {
  5. return Restangular
  6. .one("sessions")
  7. .withHttpConfig({bypassErrorInterceptor: bypassErrorInterceptor})
  8. .customPOST(data);
  9. }
  10. };
  11. return Session;
  12. })

上面的 Session 工厂有一个 create 方法返回这个对象。我们将用这个 create 方法来授权一个用户的 email 和密码。你将注意到 create 方法有一个参数叫做 bypassErrorInterceptor。这个参数应该是一个布尔类型的值(true or false),当 bypassErrorInterceptor 是 true 的时候,它将绕过我们早先在配置块中定义的 error 拦截器。这不会立即显得有意义,但是在后面我们将遇到一个场景,在那我们需要绕过 error 拦截器。

User

在 blog/client/js/factories 目录下创建一个新文件,并命名为 User.js,然后把以下代码拷贝和粘贴进文件:

</>复制代码

  1. Blog.factory("User", function(Restangular) {
  2. var User;
  3. User = {
  4. create: function(user) {
  5. return Restangular
  6. .one("users")
  7. .customPOST(user);
  8. }
  9. };
  10. return User;
  11. }

User 工厂将有一个 create 方法返回该对象。我们将使用 create 方法创建一个 user。

Services

我们的应用需要一个服务,该服务包含登录,登出,和校验该用户是否被授权的业务逻辑。我们把该服务命名为 AuthService。在 blog/cient/js/services 目录下创建一个新文件,并命名为 AuthService.js,然后拷贝和粘贴一下代码到文件中:

</>复制代码

  1. Blog.service("AuthService", AuthService = function($q, localStorageService, Session) {
  2. this.login = function(credentials) {
  3. var me = this;
  4. deferred = $q.defer()
  5. Session.create(credentials, true).then(function(user) {
  6. me.setToken(credentials);
  7. return deferred.resolve(user);
  8. }, function(response) {
  9. if (response.status == 401) {
  10. return deferred.reject(false);
  11. }
  12. throw new Error("No handler for status code " + response.status);
  13. });
  14. return deferred.promise
  15. };
  16. this.logout = function() {
  17. localStorageService.clearAll();
  18. };
  19. this.isAuthenticated = function() {
  20. var token = this.getToken();
  21. if (token) {
  22. return true
  23. }
  24. return false;
  25. };
  26. this.setToken = function(credentials) {
  27. localStorageService.set("token", btoa(credentials.email + ":" + credentials.password));
  28. };
  29. this.getToken = function() {
  30. return localStorageService.get("token");
  31. };
  32. return this;
  33. }

以上的 login 方法使用 Session.create 认证用户的认证信息。当登录认证信息是有效的,我们将保存 base64 编码格式的 email 和密码到本地存储稍后使用,并授权。当登录认证信息是无效的(当登录认证信息是无效的,REST API 将返回一个 401 状态码),我们简单的拒绝授权,并且让使用该方法的代码处理它。看一下我们传递给 Session.create 的第二个参数。我们传递一个布尔类型(true),这个就是我们早先讨论的 bypassErrorInterceptor 的参数。我们需要绕过在配置块中的 error 拦截器,因为如果我们不这样做,处理 401 错误码的 handler 将把用户跳转到登录视图。我们想这个发生在其他场景,但当我们正在登录视图并且用户提交了错误的认证信息的时候,我们想展示一个错误消息,比如“Incorrect login credentials. Please try again.”。这就是为什么我们需要绕过默认的 error 拦截器,使用一个新的,包含了不同的业务逻辑(检查以上代码的 10 - 15 行)。

logout 方法将清理保存在本地存储的数据,isAuthenticated 方法仅仅通过检查 token 是否被呈现来检查当前用户是否被授权。setToken 方法保存一个 token (base64 编码格式的 email 和 password)到本地存储中,getToken 方法获取保存在本地存储中的 token。

Directives

我们应用的 registration form 将有一个 password 和 一个确认 password 字段,因此我们需要一个方法来校验值是否匹配。为这个我们将使用 directive,在 blog/client/js/directives 目录下创建一个新文件,并命名为 match.js,然后拷贝和粘贴一下代码到文件中:

</>复制代码

  1. Blog.directive("match", function () {
  2. return {
  3. require: "ngModel",
  4. restrict: "A",
  5. scope: {
  6. match: "="
  7. },
  8. link: function(scope, elem, attrs, ctrl) {
  9. scope.$watch(function() {
  10. return (ctrl.$pristine && angular.isUndefined(ctrl.$modelValue)) || scope.match === ctrl.$modelValue;
  11. }, function(currentValue) {
  12. ctrl.$setValidity("match", currentValue);
  13. });
  14. }
  15. };
  16. }

信任以上 directive 的作者。我是从别处抓取来了以上源码,但是我没有忘记链接到该站点和谁是作者。

我们现在完成了我们 AngularJS 应用程序的一半。我们还没有写的剩下部分是控制层和模板。因为这篇文章太长了,我决定在第三部分写控制层和模板部分。同时,研究目前为止我们已经编写的代码,让我们更熟悉它。再使用 Flask 和 AngularJS 构建博客教程系列的第三部分见。

所有的 REST API 源码都在 Github 上 - https://github.com/basco-johnkevin/building-a-blog-using-flask-and-angularjs ,但是 AngularJS 应用程序的源码还没有放上去,因为我们还没有完成它,等到第三部分写完以后再放全部的源码到 Github 上。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/45297.html

相关文章

  • 使用 Flask AngularJS 构建博客 - 1

    摘要:注原文作者,原文地址为在这个教程中,我们将使用和构建一个博客。在开发期间,这将允许我们把它们运行在不同的端口例如和。现在我们将进入目录并使用运行这个脚本。示例创建一篇文章为了创建一篇文章,你需要发送一个请求给。 注:原文作者 John Kevin M. Basco,原文地址为 Building a blog using Flask and AngularJS Part 1 在...

    刘玉平 评论0 收藏0
  • 使用 Flask AngularJS 构建博客 - 1

    摘要:注原文作者,原文地址为在这个教程中,我们将使用和构建一个博客。在开发期间,这将允许我们把它们运行在不同的端口例如和。现在我们将进入目录并使用运行这个脚本。示例创建一篇文章为了创建一篇文章,你需要发送一个请求给。 注:原文作者 John Kevin M. Basco,原文地址为 Building a blog using Flask and AngularJS Part 1 在...

    lavnFan 评论0 收藏0
  • MongoDB 资源、库、工具、应用程序精选列表中文版

    摘要:推荐阅读资源库工具应用程序精选列表中文版有哪些鲜为人知,但是很有意思的网站一份攻城狮笔记每天搜集上优秀的项目一些有趣的民间故事超好用的谷歌浏览器油猴插件合集目录资源文档文章图书会谈教程更多库工具管理数据部署桌面发展监控应用资源文档介绍文档教 推荐阅读 MongoDB 资源、库、工具、应用程序精选列表中文版 有哪些鲜为人知,但是很有意思的网站? 一份攻城狮笔记 每天搜集 Github ...

    e10101 评论0 收藏0
  • 【译】《精通使用AngularJS开发Web App》(一) --- 相关背景、社区、工具介绍

    摘要:下一篇译精通使用开发二原版书名第一章之道这一章主要是介绍,包括这个框架以及它背后的项目。幸运的是,拥有一个活跃的,支持度高的社区。另外,社区还为已经存在的工具箱里贡献了许多有意思的工具。 下一篇:【译】《精通使用AngularJS开发Web App》(二) 原版书名:Mastering Web Application Development with AngularJS Ch...

    ddongjian0000 评论0 收藏0
  • Day 25: 联合Tornado、MongoDB AngularJS 进行应用开发

    摘要:在被收购之后,维护并继续发展。设置是告诉应用在目录寻找应用模板。设置告诉应用使用目录里面的类似图像文件等静态文件。我们会在应用开发过程中,保持着调试器在后台运行。这能提供高效的开发环境。我们会把回应状态设为已创建。 编者注:我们发现了有趣的系列文章《30天学习30种新技术》,正在翻译,一天一篇更新,年终礼包。下面是第23天的内容。 今天的《30天学习30种新技术》,我决定暂时放下...

    hsluoyz 评论0 收藏0

发表评论

0条评论

awokezhou

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<