• 16.flask博客项目实战十一之Bootstrap美化站点


    配套视频教程

    本文B站配套视频教程

    本章将学习基于Bootstrap用户界面框架的模板替换基础的HTML模板。

    这个Microblog应用程序已有一段时间了,或许已经注意到,并没有花时间来美化它的页面。所有的模板是使用的基础样式,没有自定义样式。这对于我们来说非常有用,可以专注于应用程序的实际逻辑,不用分心去编写好看的HTML和CSS代码。

    目前已经专注于应用程序的后端部分有一段时间了。因此,本章将暂停一下,并花一些时间来学习 如何使应用程序看起来更加精致、专业。

    本章将与之前的章节略有不同,因为不会像往常那样详细地关注Python方面。创建漂亮的网页是一个很大的主题,而与Python web后端开发很大程度上无关,因此将讨论一些基本指导和想法,将重新设计应用的外观来研究和学习它。

    CSS框架

    虽然我们可以说 编码很难,但与网页设计师相比,我们的痛苦还是无足轻重的,毕竟网页设计师必须让网页在所有Web浏览器上呈现良好一致外观的模板。近年来,变得越来越好,但在某些浏览器上仍然存在一些模糊的错误或奇怪的设定,使得设计网页的任务还是很困难。如果还要兼容屏幕限制设备(如平板电脑、智能手机)上的浏览器,则更加困难。

    如果你像我一样,只是一个想创建规范网页的开发人员,没有时间或兴趣去学习底层机制,并通过编写原生HTML和CSS来实现它,那么唯一可行的解决方案是使用CSS框架来简化任务。通过这条路径,将失去一些操作自由,但另一方面,我们的网页在所有浏览器中会看起来很不错,而也不需要花费太多精力。CSS框架为普通类型的用户界面元素提供了高级CSS类的集合,其中包含预定义样式。这些框架中的大多数还为 不能使用HTML和CSS严格执行的操作提供JavaScript插件。

    介绍Bootstrap

    最受欢迎的一个CSS框架是由Twitter建立的Bootstrap。如果想看使用这个框架设计的页面类型,可查看文档中的示例

    使用Bootstrap为网页设置样式的好处:

    1. 在所有主流Web浏览器上看起来相似;
    2. 处理台式机、平板电脑、手机屏幕尺寸;
    3. 可定制布局;
    4. 风格精美的导航栏、表单、按钮、提示、弹出窗口等。

    使用Bootstrap最直接方法是在基础模板中导入 bootstrap.min.css文件。可以下载这个文件副本,并将其添加到项目中;也可以直接从CDN导入(这里查看)。然后,可以根据文档开始使用它提供的通用CSS类。还可以导入包含框架JavaScript代码的bootstrap.min.js文件,以便使用更高级的功能。

    幸运的是,有一个名为 Flask-BootstrapFlask扩展,它提供一个随时可用的基本模板,它安装了Bootstrap框架。安装这个扩展:版本3.3.7.1;附带安装dominate 2.3.1(用于使用优雅的DOM API创建和操作HTML文档)、visitor 0.1.3(一个微型Pythonic访客实现)。

    (venv) D:microblog>pip install flask-bootstrap
    Collecting flask-bootstrap
      Using cached https://files.pythonhosted.org/packages/88/53/958ce7c2aa26280b7fd7f3eecbf13053f1302ee2acb1db58ef32e1c23c2a/Flask-Bootstrap-3.3.7.1.tar.gz
    Requirement already satisfied: Flask>=0.8 in d:microblogvenvlibsite-packages (from flask-bootstrap)
    Collecting dominate (from flask-bootstrap)
      Using cached https://files.pythonhosted.org/packages/43/b2/3b7d67dd59dab93ae08569384b254323516e8868b453eea5614a53835baf/dominate-2.3.1.tar.gz
    Collecting visitor (from flask-bootstrap)
      Using cached https://files.pythonhosted.org/packages/d7/58/785fcd6de4210049da5fafe62301b197f044f3835393594be368547142b0/visitor-0.1.3.tar.gz
    Requirement already satisfied: click>=5.1 in d:microblogvenvlibsite-packages (from Flask>=0.8->flask-bootstrap)
    Requirement already satisfied: itsdangerous>=0.24 in d:microblogvenvlibsite-packages (from Flask>=0.8->flask-bootstrap)
    Requirement already satisfied: Werkzeug>=0.14 in d:microblogvenvlibsite-packages (from Flask>=0.8->flask-bootstrap)
    Requirement already satisfied: Jinja2>=2.10 in d:microblogvenvlibsite-packages (from Flask>=0.8->flask-bootstrap)
    Requirement already satisfied: MarkupSafe>=0.23 in d:microblogvenvlibsite-packages (from Jinja2>=2.10->Flask>=0.8->flask-bootstrap)
    Installing collected packages: dominate, visitor, flask-bootstrap
      Running setup.py install for dominate ... done
      Running setup.py install for visitor ... done
      Running setup.py install for flask-bootstrap ... done
    Successfully installed dominate-2.3.1 flask-bootstrap-3.3.7.1 visitor-0.1.3
    
    

    使用Flask-Bootstrap

    Flask-Bootstrap和大多数其他Flask扩展一样,需要进行初始化:

    app/init.py:添加Flask-Bootstrap实例

    # ...
    from flask_bootstrap import Bootstrap
    
    app = Flask(__name__)
    # ...
    mail = Mail(app)
    bootstrap = Bootstrap(app)
    
    #...
    
    

    初始化扩展后,bootstrap/base.html模板变为可用,并可以使用extends子句从应用程序模板中引用。

    但是记得,我已经使用了extends子句继承自己的基础模板,这允许将页面的公共部分放在一个地方。我的 base.html模板定义了导航栏,其中包含一些链接,还导出了一个content块。目前为止,应用程序中所有其他模板都是从基础模板继承,并为content块提供页面的主要内容。

    那么,该如何适应 Bootstrap基础模板呢?想法是使用三级层次结构,而不是两层。bootstrap/base.html模板提供页面的基本结构,其中包含Bootstrap框架文件。这个模板 为了派生模板导出几个块,如titlenavbarcontent(查看完整的 块列表)。我将更改我的base.html模板,让它从bootstrap/base.html模板派生,并为titlenavbarcontent块提供实现。反过来,base.html将为了它派生模板去定义页面内容而导出自己的app_content块。

    下方将看到base.html在修改后如何从Bootstrap基础模板继承。
    app/templates/base.html:重新设计基础模板

    {% extends "bootstrap/base.html" %}
    
    {% block title %}
    	{% if title %}
    		{{ title }} - Microblog
    	{% else %}
    		Welcome to Microblog
    	{% endif %}
    {% endblock %}
    
    {% block navbar %}
    	<nav class="navbar navbar-default">
    		<div class="container">
    			<div class="navbar-header">
    				<button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1" aria-expanded="false">
    					<span class="sr-only">Toggle navigation</span>
    					<span class="icon-bar"></span>
    					<span class="icon-bar"></span>
    					<span class="icon-bar"></span>
    				</button>
    				<a class="navbar-brand" href="{{ url_for('index') }}">Microblog</a>
    			</div>
    
    			<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
    				<ul class="nav navbar-nav">
    					<li><a href="{{ url_for('index') }}">Home</a></li>
    					<li><a href="{{ url_for('explore') }}">Explore</a></li>
    				</ul>
    
    				<ul class="nav navbar-nav navbar-right">
    					{% if current_user.is_anonymous %}
    						<li><a href="{{ url_for('login') }}">Login</a></li>
    					{% else %}
    						<li><a href="{{ url_for('user', username=current_user.username) }}">Profile</a></li>
    						<li><a href="{{ url_for('logout') }}">Logout</a></li>
    					{% endif %}
    				</ul>
    			</div>
    		</div>
    	</nav>
    {% endblock %}
    
    {% block content %}
    	<div class="container">
    		{% with messages = get_flashed_messages() %}
    			{% if messages %}
    				{% for message in messages %}
    					<div class="alert alert-info" role="alert">{{ message }}</div>
    				{% endfor %}
    			{% endif %}
    		{% endwith %}
    
    		{# application content needs to be provided in the app_content block #}
    		{% block app_content %}{% endblock %}
    	</div>
    {% endblock %}
    
    

    在上述代码中,可看到如何从bootstrap/base.html派生这个模板,然后分别实现页面标题、导航栏、页面内容 三个块。

    title 块 使用 <title>标签 定义将用于页面标题的文本。对于个块,只是移动了在原始基础模板中<title>标签内的逻辑。

    navbar 块 是可选块,可用于定义导航栏。对于这个块,在Bootstrap导航栏文档中的改写了这个示例,以便它在左端包含站点标记,接着是 Home 和Explore链接。然后,添加了与页面右边框对齐的Profile和Login或Logout链接。

    最后,在content块中定义了一个顶级容器,在其中编写了渲染 闪烁消息的逻辑,现在它们显示为Bootstrap警报。接着是一个新的app_content块,它被定义为 只有派生模板才能定义它们自己的内容。

    所有页面模板的原始版本 都在名为content的块中定义它们的内容。如上所示,名为content的块被Flask-Bootstrap所使用,因此将内容块 重命名为app_content。所以,我们所有的模板都必须重命名才能去使用app_content块 作为它们的内容块。例如,如下是404.html模板 如何修改版本的演示:

    {% extends "base.html" %}
    
    {% block app_content %}
    	<h1>File Not Found</h1>
    	<p>
    		<a href="{{ url_for('index') }}">Back</a>
    	</p>
    {% endblock %}
    
    

    剩余的所有其他模板一一按上例修改即可。

    渲染Bootstarp表单

    Flask-Bootstrap有一个很棒的工作是渲染表单。Flask-Bootstrap不用逐一设置表单字段的样式,而是附带一个宏(macro),它接受一个Flask-WTF表单对象作为参数,并使用Bootstrap样式呈现完整的表单。

    下方将看到重新设计的register.html模板作为一个示例:
    app/templates/register.html

    {% extends "base.html" %}
    {% import 'bootstrap/wtf.html' as wtf %}
    
    {% block app_content %}
    	<h1>Register</h1>
    	<div class="row">
    		<div class="col-md-4">
    			{{ wtf.quick_form(form) }}
    		</div>
    	</div>
    {% endblock %}
    
    

    上述代码中,在顶部声明的import工作原理与在模板上边的一个Python import类似。这还添加了一个wtf.quick_form()宏,它在一行简单代码中呈现完整的表单,包括支持显示验证错误,并且所有样式都适合Bootstrap框架。

    以下展示为应用程序中的其他表单所做的更改:
    app/templates/edit_profile.html

    {% extends "base.html" %}
    {% import 'bootstrap/wtf.html' as wtf %}
    
    {% block app_content %}
    	<h1>Edit Profile</h1>
    	<div class="row">
    		<div class="col-md-4">
    			{{ wtf.quick_form(form) }}
    		</div>
    	</div>
    {% endblock %}
    
    

    app/templates/index.html

    {% extends "base.html" %}
    {% import 'bootstrap/wtf.html' as wtf %}
    
    {% block app_content %}
    	<h1>Hello,{{ current_user.username }}!</h1>
    	{% if form %}
    	{{ wtf.quick_form(form) }}
    	<br>
    	{% endif %}
    
    	{% for post in posts %}
    		{% include '_post.html' %}
    	{% endfor %}
    
    	<nav aria-label="...">
    		<ul class="pager">
    			<li class="previous{% if not prev_url %} disabled{% endif %}">
    				<a href="{{ prev_url or '#' }}">
    					<span aria-hidden="true">&larr;</span> Newer posts
    				</a>
    			</li>
    
    			<li class="next{% if not next_url %} disabled{% endif %}">
    				<a href="{{ next_url or '#' }}">
    					Older posts <span aria-hidden="true">&rarr;</span>
    				</a>
    			</li>
    		</ul>
    	</nav>
    
    {% endblock %}
    
    

    app/templates/login.html

    {% extends "base.html" %}
    {% import 'bootstrap/wtf.html' as wtf %}
    
    {% block app_content %}
        <h1>Sign In</h1>
        <div class="row">
            <div class="col-md-4">
                {{ wtf.quick_form(form) }}
            </div>
        </div>
        <br>
    
        <p>New User? <a href="{{ url_for('register') }}">Click to Register!</a></p>
        <p>
            Forgot Your Password?
            <a href="{{ url_for('reset_password_request') }}">Click to Reset It</a>
        </p>
    {% endblock %}
    
    

    app/templates/reset_password.html

    {% extends "base.html" %}
    {% import 'bootstrap/wtf.html' as wtf %}
    
    {% block app_content %}
    	<h1>Reset Your Password</h1>
    	<div class="row">
    		<div class="col-md-4">
    			{{ wtf.quick_form(form) }}
    		</div>
    	</div>
    {% endblock %}
    
    

    app/templates/reset_password_request.html

    {% extends "base.html" %}
    {% import 'bootstrap/wtf.html' as wtf %}
    
    {% block app_content %}
    	<h1>Reset Password</h1>
    	<div class="row">
    		<div class="col-md-4">
    			{{ wtf.quick_form(form) }}
    		</div>
    	</div>
    {% endblock %}
    
    

    app/templates/user.html

    {% extends "base.html" %}
    
    {% block app_content %}
    	<table class="table table-hover">
    		<tr>
    			<td width="256px"><img src="{{ user.avatar(256) }}"></td>
    			<td>
    				<h1>User:{{ user.username }}</h1>
    				{% if user.about_me %}
    					<p>{{ user.about_me }}</p>
    				{% endif %}
    
    				{% if user.last_seen %}
    					<p>Last seen on:{{ user.last_seen }}</p>
    				{% endif %}
    
    				{% if user == current_user %}
    					<p>
    						<a href="{{ url_for('edit_profile') }}">Edit your profile</a>
    					</p>
    
    				{% elif not current_user.is_following(user) %}
    					<p>
    						<a href="{{ url_for('follow', username=user.username) }}">Follow</a>
    					</p>
    
    				{% else %}
    					<p>
    						<a href="{{ url_for('unfollow', username=user.username) }}">Unfollow</a>
    					</p>
    				{% endif %}
    			</td>
    		</tr>
    	</table>
    
    	{% for post in posts %}
    		{% include '_post.html' %}
    	{% endfor%}
    
    	<nav aria-label="...">
    		<ul class="pager">
    			<li class="previous{% if not prev_url %} disabled{% endif %}">
    				<a href="{{ prev_url or '#' }}">
    					<span aria-hidden="true">&larr;</span> Newer posts
    				</a>
    			</li>
    			<li class="next{% if not next_url %} disabled{% endif %}">
    				<a href="{{ next_url or '#' }}">
    					Older posts <span aria-hidden="true">&rarr;</span>
    				</a>
    			</li>
    		</ul>
    	</nav>
    {% endblock%}
    
    

    博客帖子的渲染

    呈现单个博客帖子的表示逻辑被抽象为 名为_post.html的子模板。对其小调整,以便在Bootstrap下看起来更好。 app/templates/_post.html

    <table class="table table-hover">
        <tr>
            <td width="70px">
                <a href="{{ url_for('user', username=post.author.username) }}">
                    <img src="{{ post.author.avatar(70) }}" />
                </a>
            </td>
            <td>
                <a href="{{ url_for('user', username=post.author.username) }}">
                    {{ post.author.username }}
                </a>
                says:
                <br>
                {{ post.body }}
            </td>
        </tr>
    </table>
    
    

    渲染分页链接

    分页链接是Bootstrap提供支持的另一个领域。为此,再一次参考Bootstrap文档,并调整其中一个示例。app/templates/index.html:重新设计的分页链接

    [← Newer posts]

    	<li class="next{% if not next_url %} disabled{% endif %}">
    			<a href="{{ next_url or '#' }}">
    				Older posts <span aria-hidden="true">&rarr;</span>
    			</a>
    		</li>
    	</ul>
    </nav>
    

    不过注意,在上述实现中,当某个方向没有更多内容时,将运用禁用状态,而不是隐藏下一个或上一个链接,这将使链接显示为灰色。

    类似的更改也需要运用于user.html,这个更改在上一小节上展示了。

    目前为止,项目结构:

    microblog/
        app/
            templates/
    	        email/
    		        reset_password.html
    		        reset_password.txt
    	        _post.html
    	        404.html
    	        500.html
                base.html
                edit_profile.html
                index.html
                login.html
                register.html
                reset_password.html
                reset_password_request.html
                user.html
            __init__.py
            email.py
            errors.py
            forms.py
            models.py
            routes.py
        logs/
            microblog.log
        migrations/
        venv/
        app.db
        config.py
        microblog.py
        tests.py
    
    

    参考
    https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xi-facelift

  • 相关阅读:
    IDEA 错误:程序包XXX不存在
    202A 202B 202C 202D 202E字符的作用及解释
    MySQL 获取每月多少日的sql写法
    Mybatis Plus使用租户过滤无效解决方案
    Shiro集成多个Realm,认证都不通过返回y configured realms. Please ensure that at least one realm can authenticate these tokens.
    使用IDEA开发SpringBoot不加载application.yml配置文件的解决方案
    集成SpringCloudBus,但没有总线通知更改
    Gradle 使用@Value注册编译报错
    Shiro Session放到Redis中常遇到的问题
    前端页面调试方式的选择
  • 原文地址:https://www.cnblogs.com/songboriceboy/p/13852071.html
Copyright © 2020-2023  润新知