Как оптимизировать шаблоны для render_template () Flask - PullRequest
0 голосов
/ 21 декабря 2018

Я создаю веб-приложение для нашего студенческого паба для управления транзакциями, инвентаризацией, клиентами и т. Д. С помощью Flask и базы данных MySQL.Однако страница индекса, на которой перечисляются до 12 клиентов на картах Bootstrap (с кнопками для оплаты, пополнения счета и т. Д.), Отрисовывает 4 секунды с помощью render_template ().

Это приложение развернуто на дроплете DigitalOcean с 2 виртуальными ЦП и обрабатывает около 50-100 одновременных клиентов в определенный момент времени, используя типичный дуэт Nginx / Gunicorn.Я пытался запустить его на своем локальном компьютере, и у меня были те же результаты: 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)

Iпо времени шаги в index(): render_template() занимают ~ 4 с, а оставшаяся функция просмотра занимает ~ 15 мс.

Что я здесь не так делаю?Мой шаблон слишком сложный?Будучи веб-любителем, я не знаю, сколько может стоить злоупотребление шаблонами 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 голосов
/ 22 декабря 2018

Как уже упоминалось в моем вопросе, я обнаружил, что виновником было выпадающее меню "Оплата", которое отображается 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})

Таким образом, я толькоОтобразите эти выпадающие списки, когда они действительно используются, и время отрисовки страницы индекса возвращается к нормальному.

Не стесняйтесь комментировать, если вы обнаружите, что это решение не идеально!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...