美文网首页
Python + React (2)

Python + React (2)

作者: 不知道是哪个号 | 来源:发表于2019-10-31 19:35 被阅读0次

    原文地址

    背景

    上篇文章我们已经把 python + react 基本环境搭建出来了,基本环境已有,下面我将仿照某个博客网站来开发一个同样的网站。

    基本环境

    系统环境: macOS系统

    使用python等版本如下

    node -v: v11.1.0
    npm -v: 6.5.0
    python3 --version: 3.6.5
    

    数据库模型配置和升级

    模型配置

    博客类型网站肯定和用户、文章、评论等相关,flask_appbuilder 已经自带用户模型,因此我们在这基础上添加两个模型 Blog 和 Comment(初版本只为实现基本功能)。其中 Blog 和 Comment 分别关联上用户(ab_user)信息, 除了分别关联上用户信息,Blog 和 Comment 之间实现了一对多关心。如下所示:

    class Blog(Model, AuditMixinNullable):
        """发布文章"""
    
        __tablename__ = 'blog'
        id = Column(Integer, primary_key=True)
        title = Column(String(500))
        body = Column(Text)
        body_html = Column(Text)
        user_id = Column(Integer, ForeignKey('ab_user.id'))
        owner = relationship(security_manager.user_model, backref='blog', foreign_keys=[user_id])
        comments = relationship('Comment', back_populates="blog")
    
        def __repr__(self):
            return self.title if self.title else self.id
    
        @property
        def simple_json(self):
            return {
                'id': self.id,
                'title': self.title,
                'body': self.body,
                'body_html': self.body_html,
                'timestamp': self.changed_on.isoformat(),
                'owner': self.owner.to_json(),
                'comments': [comment.simple_json for comment in self.comments]
            }
    
    class Comment(Model, AuditMixinNullable):
        """发布文章评论"""
    
        __tablename__ = 'comments'
        id = Column(Integer, primary_key=True)
        body = Column(Text)
        body_html = Column(Text)
        user_id = Column(Integer, ForeignKey('ab_user.id'))
        owner = relationship(security_manager.user_model, backref='comments', foreign_keys=[user_id])
        blog_id = Column(Integer, ForeignKey('blog.id'))
        blog = relationship(Blog, back_populates="comments")
        disabled = Column(Boolean, default=False)
    
        def __repr__(self):
            return self.body if self.body else self.body_html
        
        @property
        def simple_json(self):
            return {
                'id': self.id,
                'body': self.body,
                'timestamp': self.changed_on.isoformat(),
                'owner': self.owner.to_json()
            }
    

    这两个模型我们都引入了一个 AuditMixinNullable,该模块继承于 flask_appbuilder 中的 AuditMixin,我们调整了 nullable 字段,使其允许使用空字段 (参考 superset), 这样我们在生成 Blog 和 Comment 模型时会生成 created_on、changed_on、created_by_fk 和changed_by_fk 四个字段。我们在 models 下新建一个helpers.py,将 AuditMixinNullable 放入该文件

    from datetime import datetime
    
    from flask_appbuilder.models.mixins import AuditMixin
    import sqlalchemy as sa
    from sqlalchemy.ext.declarative import declared_attr
    
    class AuditMixinNullable(AuditMixin):
    
        created_on = sa.Column(sa.DateTime, default=datetime.now, nullable=True)
        changed_on = sa.Column(sa.DateTime, default=datetime.now, onupdate=datetime.now, nullable=True)
    
        @declared_attr
        def created_by_fk(self):
            return sa.Column(
                sa.Integer,sa.ForeignKey("ab_user.id"),default=self.get_user_id,nullable=True,
            )
    
        @declared_attr
        def changed_by_fk(self):
            return sa.Column(
                sa.Integer,sa.ForeignKey("ab_user.id"),default=self.get_user_id,onupdate=self.get_user_id,nullable=True,
            )
    
    

    调整后的目录结构如下所示:

    38-1.png

    模型升级

    以上配置都完成后我们开始升级数据库,我们进入到项目对应目录下,启动虚拟环境并设置环境变量,然后执行 flask db migrate 命令,如下图所示:

    38-2.png

    执行完成后我们会在 migration/versions/ 目录下发现新生成的一个文件,检查该文件内容,确定是否需要作出调整(一般是不需要更改)然后我们执行下面命令

    flask db upgrade
    

    以上命令都执行完成后,数据库就生成了对应的 blog 和 comment 两张表。

    网站建立

    样式抓取

    用 Google 浏览器打开需要抓取网站的地址,使用 Command + S 快捷键保存该页面内容到本地文件下(其他浏览器保存方式自行查找)。我们发现文件下会出现一个 .htm 结尾的文件和一个 _files 结尾的文件夹。打开文件夹,我们发现里面大多是是图片,删除里面除 .css 和 .js 结尾的其他文件。如下所示:

    38-3.png

    抓取下来的 .htm 和 .css 文件内容都是错位的,为了方便查看我们使用工具将这两个文件分别格式化。这样就完成了该页面样式的抓取。

    页面分析

    用浏览器打开 .htm 文件,如下图所示:

    38-4.png

    分析该网站发现该页面除了导航栏主要分左右两个模块,左侧由头部轮播和文章列表两部分组成,右侧主要由banner和推荐作者两个模块组成。第一个版本我们只做文章列表和推荐作者这两个模块。

    页面搭建

    首页对应我们的 assets/src/welcome 模块,从上面分析知该模块我们需要的文章列表和推荐用户列表两大数据。

    App.js

    新增 blogList 和 recommendUsers 两大数据源,并将数据传入到 welcome.jsx 模块 具体代码如下所示:

    App.js

    const container = document.getElementById('app');
    const bootstrap = JSON.parse(container.getAttribute('data-bootstrap'));
    /** 当前用户信息 **/
    const user = { ...bootstrap.user };
    /** 博客列表 **/
    const blogList = { ...bootstrap.blogList };
    /** 推荐用户列表 **/
    const recommendUsers = { ...bootstrap.recommendUsers };
    
    const store = createStore(
      combineReducers({
        messageToasts: messageToastReducer,
      }),
      {},
      compose(
        applyMiddleware(thunk),
        initEnhancer(false),
      ),
    );
    
    const App = () => (
      <Provider store={store}>
        <Welcome user={user} blogList={blogList} recommendUsers={recommendUsers}/>
      </Provider>
    );
    export default hot(module)(App);
    

    Welcome.jsx

    我们从 App.jsx 获取到数据后,我们进入主页面搭建过程,分析 .htm 文件我们知道我们需要两个模块的数据在 container index 中, 而博客列表数据在画红线的 col-xs-16 main 中,推荐用户列表在画蓝线的 col-xs-7 col-xs-offset-1 aside 中 如下图所示:

    38-5.png

    我们将博客列表拆分到 BlogList 中并将博客数据传入该模块,同理推荐用户拆分到 RecommendList 中并把推荐用户数据传入该模块,Welcome.jsx 中代码如下所示:

      render() {
        const blogList = this.props.blogList;
        const recommendUsers = this.props.recommendUsers;
        const user = this.props.user;
        return (
          <div className="container index">
            <div className="row">
              <BlogList blogList={blogList} user={user} />
              <RecommendList recommendUsers={recommendUsers} />
            </div>
          </div>
        );
      }
    

    BlogList.jsx、BlogListRow.jsx、BlogListRowContent.jsx

    现在我们分析 .htm 中 col-xs-16 main 模块,该模块主要是博客内容列表,这个版本我们不考虑头部的轮播图,因此我们直接找到 list-container 下的 note-list, 该模块由多个 li 组成,观察 li 的内容我们发现,该模块主要有两种类型,一种是带图片的,一种是无图的,核心样式都是一样的,因此我们可以拆分一个 BlogListRow.jsx ,在这个类中去区分有无图片这个问题。将核心的列表样式都放到 BlogListRowContent.jsx,这样blog这个模块的代码基本就可以确定下来了如下所示:

    BlogList.jsx

      render() {
        const blogList = this.props.blogList;
        let data = Object.keys(blogList).map( key => blogList[key]);
        return (
          <div className="col-xs-16 main">
            <div className="split-line"> </div>
            <div id="list-container">
              <ul className="note-list">
               {data.map( (blog) => (
                 <BlogListRow key={blog.id} blog={blog} user={this.props.user} />)
               )}
              </ul>
            </div>
          </div>
        )
      }
    

    BlogListRow.jsx

      render() {
        const blog = this.props.blog;
        /** list has image **/
        const hasImg = false;
        if (hasImg) {
          return (
            <li className="hav-img">
              <a className="wrap-img" href="#">
                <img src="" className="img-blur" alt="120"/>
              </a>
              <BlogListRowContent blog={blog} user={this.props.user} />
            </li>
          )
        } else {
          return (
            <li>
              <BlogListRowContent blog={blog} user={this.props.user} />
            </li>
          )
        }
      }
    

    BlogListRowContent.jsx

      render() {
        const blog = this.props.blog;
        const owner = blog.owner;
        return (
          <div className="content">
            <a className="title" href="">
              {blog.title}
            </a>
            <p className="abstract">
              {blog.body}
            </p>
            <div className="meta">
              <a className="nickname" href="">
                {owner.username}
              </a>
              <a href="">
                <i className="iconfont ic-list-comments"> </i> 7
              </a>
              <span>
                <i className="iconfont ic-list-like"> </i> 10
              </span>
            </div>
          </div>
        )
      }
    

    我们只是把 .htm 的样式代码部分复制过来了,但是核心的 css 样式还没有引入到项目中,我们可以在 assets/stylesheets/ 目录下我们新建一个 welcome.css 文件来管理我们的 css 样式,打开我们刚刚保存的网站样式我们看到里面有两个 css 结尾的文件,我们打开以 entry 开头的 css 文件,发现里面有 .note-list.recommended-authors 等一些样式,这些刚好和我们刚刚 blog 里面的代码样式能对应上,因此我们将该内容粘贴到 welcome.css 样式中,在打开 web 开头到文件,里面有 1w+ 行代码,粗略看了下,这里面的样式和页面的基本样式有关,因此我们将该文件内容放入基本样式文件 ddblog.less 中,并在 theme.js文件中引入该文件(后期在做调整)。这样首页的样式就配置成功了,最后我们在 welcome.jsx 文件中引入刚刚配置的 welcome.css 文件。

    RecommendList.jsx RecommendRowContent.jsx

    推荐用户部分的可以参考上面部分完成具体内容如下:

    RecommendList.jsx

      render() {
        const recommendUsers = this.props.recommendUsers;
        if (recommendUsers == null || recommendUsers.length == 0) {
          return (
            <div> </div>
          )
        }
        let data = Object.keys(recommendUsers).map( key => recommendUsers[key]);
        return (
          <div className="col-xs-7 col-xs-offset-1 aside">
            <div className="recommended-author-wrap">
              <div className="recommended-authors">
                <div className="title">
                  <span>{"推荐作者"}</span>
                  <a className="page-change">
                    <i className="iconfont ic-search-change"> {"换一批"} </i>
                  </a>
                </div>
                <ul className="list">
                  {data.map( (user) => (
                  <RecommendRowContent key={user.id} user={user} />)
                  )}
                </ul>
                <a href="" className="find-more">
                  {"查看全部"}
                </a>
              </div>
            </div>
          </div>
        );
    

    RecommendRowContent.jsx

      render() {
        const user = this.props.user;
        return (
          <li>
            <a href="" className="avatar">
            </a>
            <a className="follow">
              <i className="iconfont ic-follow"> </i> {"关注"}
            </a>
            <a href="" className="name">
              {user.username}
            </a>
            <p>
              {"2.1k喜欢"}
            </p>
          </li>
        );
      }
    

    npm 打包

    以上操作都完成后,我们运行以下命令:

    npm run dev
    
    

    如果没有出现任何红色警告或者提示,那么恭喜你可以进入下一步。

    数据提供

    上面我们已经把首页的样式完成,现在我们需要为首页提供数据。首页的入口我们上次配置在 views/core.py 文件中,打开该文件找到 welcome 接口, 新增查找博客和用户列表数据库操作,具体代码如下所示:

        @expose('/welcome')
        def welcome(self):
    
            blog_list = (
                db.session.query(models.Blog).all()
            )
            if blog_list:
                blog_list = [blog.simple_json for blog in blog_list]
            recommend_list = (
                db.session.query(security_manager.user_model).all()
            )
            if recommend_list:
                recommend_list = [recommend.to_json() for recommend in recommend_list]
            payload = {
                'common': self.common_bootstrap_payload(),
                'user': bootstrap_user_data(),
                'blogList': blog_list or [],
                'recommendUsers': recommend_list or []
            }
            return self.render_template(
                'blog/basic.html',
                entry='welcome',
                bootstrap_data=json.dumps(payload)
            )
    

    注意 payload 中的 key(blogList,recommendUsers,user) 需要和 App.js 中的相对应。

    小结

    以上都配置都没出现错误,我们就可以成功启动项目了。附上一张成功启动的图

    38-6.png

    相关文章

      网友评论

          本文标题:Python + React (2)

          本文链接:https://www.haomeiwen.com/subject/spztbctx.html