Templating

This will cover various methods used in our jinja templates.

Base.html

{% import 'macros/nav_macros.html' as nav %}

<!DOCTYPE html>
<html>
    <head>
        {% include 'partials/_head.html' %}
        {# Any templates that extend this template can set custom_head_tags to add scripts to their page #}
        {% block custom_head_tags %}{% endblock %}
    </head>
    <body>
      {# Example dropdown menu setup. Uncomment lines to view
        {% set dropdown = 
          [
            ('account stuff',
              [
                ('account.login', 'login', 'sign in'),
                ('account.logout', 'logout', 'sign out'),
                ('2nd drop', [
                  ('account.login', 'login 2', ''),
                  ('3rd drop', [
                    ('main.index', 'home 2', '')
                  ])
                ])
              ]
            ),
            ('main.index', 'home 1', 'home')
          ]
        %}
      #}

        {% block nav %}
          {# add dropdown variable here to the render_nav method to render dropdowns #}
          {{ nav.render_nav(current_user) }}
        {% endblock %}

        {% include 'partials/_flashes.html' %}
        {# When extended, the content block contains all the html of the webpage #}
        {% block content %}
        {% endblock %}

        {# Implement CSRF protection for site #}
        {% if csrf_token() %}
            <div style="visibility: hidden; display: none">
                <input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
            </div>
        {% endif %}
    </body>
</html>

Macros: Password Strength (check_password.html)

Refer to app/templates/macros/check_password.html

This uses the zcvbn password checker to check the entropy of the password provided in the password field. Given a specified field, the password checker will check the entropy of the field and disable the submit button until the give 'level' is surpassed

Macros: Form rendering (render_form)

{% macro render_form(form, method='POST', extra_classes='', enctype=None) %}
    {% set flashes = {
        'error':   get_flashed_messages(category_filter=['form-error']),
        'warning': get_flashed_messages(category_filter=['form-check-email']),
        'info':    get_flashed_messages(category_filter=['form-info']),
        'success': get_flashed_messages(category_filter=['form-success'])
    } %}

    {{ begin_form(form, flashes, method=method, extra_classes=extra_classes, enctype=enctype) }}
        {% for field in form if not (is_hidden_field(field) or field.type == 'SubmitField') %}
            {{ render_form_field(field) }}
        {% endfor %}

        {{ form_message(flashes['error'], header='Something went wrong.', class='error') }}
        {{ form_message(flashes['warning'], header='Check your email.', class='warning') }}
        {{ form_message(flashes['info'], header='Information', class='info') }}
        {{ form_message(flashes['success'], header='Success!', class='success') }}

        {% for field in form | selectattr('type', 'equalto', 'SubmitField') %}
            {{ render_form_field(field) }}
        {% endfor %}
    {{ end_form(form) }}
{% endmacro %}

Render a flask.ext.wtforms.Form object.

Parameters:
    form          – The form to output.
    method        – <form> method attribute (default 'POST')
    extra_classes – The classes to add to the <form>.
    enctype       – <form> enctype attribute. If None, will automatically be set to
                    multipart/form-data if a FileField is present in the form. 

Render Form renders a form object. It calls the begin form macro. Initially a 'flashes' variable is set with 'error', 'warning', 'info', 'success' which have values gathered from the get_flashed_messages method from flask. Note that all flashes are stored in SESSIOn with a category type. For most of our purposes, we only have form-error and form-success as our flash types (the second parameter in the flash function call seen in the views.

Then the begin_form macro is called and for each form field in the provided form render_form_field macro is called with the field. All hidden fields (i.e. the CSRF field) and all submit fields is not rendered at this fime in render_form_field. In the render_form_field method, render_form_input is called for each input in the form field.

After that, the form_message macro is called with each of the flash types.

Lastly, the submit field is rendered. And the form is closed with the end_form macro

Macros: Start Form (begin_form)

Set up the form, including hidden fields and error states. begin_form is called from render_form. First a check is performed to check if there exists a field within the form with type equal to FileField. This check is performed via filter ("|") in Jinja. This initial check produces a filtered object, the 'list' filter creates a iterable list which we can then check the length of with 'length > 0'. So if this check passes, then the enctype must be set to multipart/form-data to accomodate a file upload. Otherwise, there is no enctype.

Then the form tag is created with a method default of POST, enctype decided by the check explained above. If there are errors (by field specific validator errors or if the flashes.error, flashes.warning, flashes.info, flashes.success is not None, then that class is added to the overall class of the form (along with any specified extra_classes, default = '').

Lastly the hidden_tags are rendered. WTForms includes in this method the rendering of the hidden CSRF field. We don't have to worry about that.

Example output:

<form action="" method="POST" enctype="multipart/form-data" class="ui form">
  <div style="display:none;">
    <input id="csrf_token" name="csrf_token" type="hidden" value="SOME_CSRF_TOKEN_HERE"> 

Macros: Flash message to Form (form_message)

Render a message for the form. This is called from the render_form macro.

Recall the get_flashed_messages method. It will get the flash message from the SESSION object with a given cateogory_filter. Within the render_form macro, the flashes variable is set with attributes 'errors', 'success', 'info', and 'warning'. The messages parameter for form_message contains the flash messages for the respective attribute specified in flashes['some_attr'].

The form_message macro is called after all form fields have been rendered, except for the Submit field. A div is created with class= 'ui CLASS message' class being either error, success, info, or warning. This div is only created if there are messages for a given flashes type! For each of the messages in the flashes type, the message is filtered to only contain escaped HTML chars and appended within the div ul as a list element.

Example Output:

<div class="ui error message">
  <div class="header">Something went wrong.</div>
  <ul class="list">
    <li>Invalid email or password.</li>
  </ul>
</div>

Macros: Render a form field (render_form_field)

Render a field for the form. This is rather self explanatory. If the field is a radio field (RadioField WTForms object) extra_classes has an added class of 'grouped fields' since all the options of a Radio Field must be styled in this way to display together. If there is a validation error on the form field, a error class is added to the field div (to make the field colored red). Then the render_form_input macro is called with field object itself as a parameter. Any validation errors are then added with a sub-dev with content field.errors (we only show the first validation error for the given error for simplicity) and filter for HTML safe chars.

Partials: _flashes

See the macros/form_macros for extended explanation of the get_flashed_messages(category_filter) method. This macro renders general flash methods that appear at the top of the page. We render by flash type and create a separate 'ui {{ class }} message' div for each message within a specific flash type. Error = red, warning = yellow, info = blue, success = green.

Partials: _head

This method contains all the assett imports (i.e. imports for scripts and styles for the app) Note that the asssets will be contained in the static/webassets-external folder when the app is in debug mode.