如何为Flask的render_template()优化模板

时间:2018-12-21 10:20:13

标签: python html templates flask jinja2

我正在为学生酒吧创建一个Web应用程序,以使用Flask和MySQL数据库管理交易,库存,客户等。但是,索引页面最多列出4个Bootstrap卡中的12个客户(带有付款按钮,充值他们的帐户等),需要3秒钟的时间使用render_template()进行渲染。

此应用程序部署在具有2个vCPU的DigitalOcean Droplet上,并使用典型的Nginx / Gunicorn组合在给定的时间处理大约50-100个同时运行的客户端。我尝试在本地计算机上运行它,并得到相同的结果:render_template()花费了很长时间才能呈现页面。

这是模板的一部分,需要4秒钟的时间来呈现:

<div class="container">
  <div class="row">
    {% for user in users.items %}
    <div class="col-lg-3 col-md-4 col-sm-6 col-6">
      <div class="card mb-4 shadow {% if not user.deposit %}text-secondary border-secondary{% elif user.balance <= 0 %}text-danger border-danger{% elif user.balance <= 5 %}text-warning border-warning{% else %}text-primary border-primary{% endif %}">
        <a href="{{ url_for('main.user', username=user.username) }}">
          <img class="card-img-top img-fluid" src="{{ user.avatar() }}" alt="{{ user.username }}">
        </a>
        <div class="card-body">
          <h5 class="card-title text-nowrap user-card-title">
            {% if user.nickname %}
            "{{ user.nickname }}"<br>{{ user.last_name|upper }}
            {% else %}
            {{ user.first_name }}<br>{{ user.last_name|upper }}
            {% endif %}
          </h5>
        </div>
        <div class="card-footer">
          {% if user.deposit %}
          <div class="btn-toolbar justify-content-between" role="toolbar" aria-label="Pay and quick access item">
            <div class="btn-group" role="group" aria-label="Pay">
              {% include '_pay.html.j2' %}
            </div>
            <div class="btn-group" role="group" aria-label="Quick access item">
              {% include '_quick_access_item.html.j2' %}
            </div>
          </div>
          {% else %}
          <div class="btn-toolbar justify-content-between" role="toolbar" aria-label="Deposit">
            <div class="btn-group" role="group" aria-label="Deposit">
              {% include '_deposit.html.j2' %}
            </div>
          </div>
          {% endif %}
        </div>
      </div>
    </div>
    {% endfor %}
  </div>
</div>

“快速访问项”,“充值”和“存款”是简单的下拉按钮,“付款”是可滚动的下拉列表,最多可以列出50种产品。该索引页面是分页的,用户是Flask分页对象,每页最多12个用户,总共100个用户。

这是索引页面的路由功能:

@bp.route('/', methods=['GET'])
@bp.route('/index', methods=['GET'])
@login_required
def index():
    """ View index page. For bartenders, it's the customers page and for clients,
    it redirects to the profile. """
    if not current_user.is_bartender:
        return redirect(url_for('main.user', username=current_user.username))

    # Get arguments
    page = request.args.get('page', 1, type=int)
    sort = request.args.get('sort', 'asc', type=str)
    grad_class = request.args.get('grad_class', str(current_app.config['CURRENT_GRAD_CLASS']), type=int)

    # Get graduating classes
    grad_classes_query = db.session.query(User.grad_class.distinct().label('grad_class'))
    grad_classes = [row.grad_class for row in grad_classes_query.all()]

    # Get inventory
    inventory = Item.query.order_by(Item.name.asc()).all()

    # Get favorite items
    favorite_inventory = Item.query.filter_by(is_favorite=True).order_by(Item.name.asc()).all()

    # Get quick access item
    quick_access_item = Item.query.filter_by(id=current_app.config['QUICK_ACCESS_ITEM_ID']).first()

    # Sort users alphabetically
    if sort == 'asc':
        users = User.query.filter_by(grad_class=grad_class).order_by(User.last_name.asc()).paginate(page,
        current_app.config['USERS_PER_PAGE'], True)
    else:
        users = User.query.filter_by(grad_class=grad_class).order_by(User.last_name.desc()).paginate(page,
            current_app.config['USERS_PER_PAGE'], True)

    return render_template('index.html.j2', title='Checkout',
                        users=users, sort=sort, inventory=inventory,
                        favorite_inventory=favorite_inventory,
                        quick_access_item=quick_access_item,
                        grad_class=grad_class, grad_classes=grad_classes)

我为index()中的步骤计时:render_template()花费了大约4秒钟,其余视图功能花费了大约15ms。

我在这里做错了什么?我的模板太复杂了吗?作为一个网络爱好者,我不知道jinja2模板会遭受多少滥用。我考虑过将模板预呈现到静态文件中,但是如果Nginx提供静态文件,那么我如何确保安全性(只有调酒师帐户才能访问此页面)?

编辑:这是包含在索引模板中的“付款”下拉模板。他是渲染时间很长的罪魁祸首。

<div class="btn-group pay-btn" role="group">
  <button class="user-card-btn btn {% if user.balance <= 0 %}btn-danger{% elif user.balance <= 5 %}btn-warning{% else %}btn-primary{% endif %} dropdown-toggle{% if user.balance <= 0 or not user.deposit %} disabled{% endif %}" type="button" id="dropdownPay" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
    <span class="icon" data-feather="shopping-cart"></span><span class="text">Pay</span>
  </button>
  <div class="dropdown-menu scrollable-menu" aria-labelledby="dropdownPay">
    {% if favorite_inventory|length > 1 %}
    <h6 class="dropdown-header">Favorites</h6>
    {% for item in favorite_inventory %}
    <a class="dropdown-item{% if (not user.deposit) or (user.can_buy(item) != True) or (item.is_quantifiable and item.quantity <= 0) %} disabled{% endif %}" href="{{ url_for('main.pay', username=user.username, item_name=item.name) }}">
      {% if (not user.deposit) or (user.can_buy(item) != True) or (item.is_quantifiable and item.quantity <= 0) %}
      <strike>
      {% endif %}
      {{ item.name }} ({{ item.price }}€)
      {% if (not user.deposit) or (user.can_buy(item) != True) or (item.is_quantifiable and item.quantity <= 0) %}
      </strike>
      {% endif %}
    </a>
    {% endfor %}
    <div class="dropdown-divider"></div>
    {% endif %}
    <h6 class="dropdown-header">Products</h6>
    {% for item in inventory %}
    {% if item not in favorite_inventory %}
    <a class="dropdown-item{% if (not user.deposit) or (user.can_buy(item) != True) or (item.is_quantifiable and item.quantity <= 0) %} disabled{% endif %}" href="{{ url_for('main.pay', username=user.username, item_name=item.name) }}">
      {% if (not user.deposit) or (user.can_buy(item) != True) or (item.is_quantifiable and item.quantity <= 0) %}
      <strike>
      {% endif %}
      {{ item.name }} ({{ item.price }}€)
      {% if (not user.deposit) or (user.can_buy(item) != True) or (item.is_quantifiable and item.quantity <= 0) %}
      </strike>
      {% endif %}
    </a>
    {% endif %}
    {% endfor %}
  </div>
</div>

1 个答案:

答案 0 :(得分:0)

正如我在问题中提到的那样,我发现罪魁祸首是“付款”下拉列表,该下拉列表每页呈现12次,并且包含许多项。为了解决该问题,我在点击时使用AJAX填充了每个下拉列表,而不是使用jinja2渲染它们:

<script>
$(".pay-btn").click(function() {
  var pay_btn = $(this)
  var dropdown = $(pay_btn).find('.dropdown-menu')
  $.post('/get_user_products', {
    username: $(pay_btn).attr('id').replace('-pay-btn', '')
  }).done(function(response) {
    $(dropdown).append(response['html'])
  }).fail(function() {
    $(dropdown).append('Error: Could not contact server.')
  });
});
</script>

我从这条路线上获得用户产品:

@bp.route('/get_user_products', methods=['POST'])
@login_required
def get_user_products():
    """ Returns the list of products that a user can buy. """
    if not current_user.is_bartender:
        flash("You don't have the rights to access this page.", 'danger')
        return redirect(url_for('main.index'))

    # Get user
    user = User.query.filter_by(username=request.form['username']).first_or_404()

    # Get inventory
    inventory = Item.query.order_by(Item.name.asc()).all()

    # Get favorite items
    favorite_inventory = Item.query.filter_by(is_favorite=True).order_by(Item.name.asc()).all()

    pay_template = render_template('_user_products.html.j2', user=user,
                                    inventory=inventory,
                                    favorite_inventory=favorite_inventory)

    return jsonify({'html': pay_template})

因此,我仅在实际使用这些下拉菜单且索引页的渲染时间恢复正常时才渲染。

如果您发现此解决方案不理想,请随时发表评论!