Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Crafting Frontend Interfaces with Jinja2 for a Flask Q&A Site

Tech May 6 60

A Q&A platform built with Flask relies on mutliple HTML pages for user registration, login, question submission, list display, and detailed view. Each template extends a common layout and uses Jinja2 to inject dynamic content while maintaining a consistent Bootstrap-based styling.

The template below implements a registration form with email, verification code, username, and password fields. The verification button triggers a client-side script to request a code.

{% extends 'layout.html' %}
{% block head %}
    <link rel="stylesheet" href="{{ url_for('static', filename='css/auth.css') }}">
    <script src="{{ url_for('static', filename='js/verification.js') }}" defer></script>
{% endblock %}
{% block title %}Create Account{% endblock %}
{% block body %}
    <h2 class="page-heading">Sign Up</h2>
    <form action="" method="post" novalidate>
        <div class="form-wrapper">
            <div class="mb-3">
                <input type="email" name="user_email" class="form-input" placeholder="Email address" required>
            </div>
            <div class="mb-3 input-group">
                <input type="text" name="verification_token" class="form-input" placeholder="Verification code">
                <button type="button" class="btn-outline" id="get-code-btn">Send Code</button>
            </div>
            <div class="mb-3">
                <input type="text" name="display_name" class="form-input" placeholder="Username" required>
            </div>
            <div class="mb-3">
                <input type="password" name="passwd" class="form-input" placeholder="Password" required>
            </div>
            <div class="mb-3">
                <input type="password" name="passwd2" class="form-input" placeholder="Confirm password" required>
            </div>
            <div class="mb-3">
                <button type="submit" class="btn-primary full-width">Register</button>
            </div>
        </div>
    </form>
{% endblock %}

The login page accepts an identity field (email or username) and a password.

{% extends 'layout.html' %}
{% block head %}
    <link rel="stylesheet" href="{{ url_for('static', filename='css/auth.css') }}">
{% endblock %}
{% block title %}Sign In{% endblock %}
{% block body %}
    <h2 class="page-heading">Log In</h2>
    <form method="post">
        <div class="form-wrapper">
            <div class="mb-3">
                <input type="text" name="identity" class="form-input" placeholder="Email or username" required>
            </div>
            <div class="mb-3">
                <input type="password" name="passwd" class="form-input" placeholder="Password" required>
            </div>
            <div class="mb-3">
                <button type="submit" class="btn-primary full-width">Sign In</button>
            </div>
        </div>
    </form>
{% endblock %}

Posting a question uses a title input and a larger text area for Description.

{% extends 'layout.html' %}
{% block head %}
    <link rel="stylesheet" href="{{ url_for('static', filename='css/editor.css') }}">
{% endblock %}
{% block title %}Ask a Question{% endblock %}
{% block body %}
    <h2 class="page-heading">New Question</h2>
    <form method="post">
        <div class="form-wrapper">
            <div class="mb-3">
                <input type="text" name="topic" class="form-input" placeholder="Question title" required>
            </div>
            <div class="mb-3">
                <textarea name="description" class="form-input" rows="5" placeholder="Describe your question..."></textarea>
            </div>
            <div class="mb-3 text-right">
                <button type="submit" class="btn-primary">Post Question</button>
            </div>
        </div>
    </form>
{% endblock %}

The home page renders a feed of questions with truncated content and metadata, each linking to its detail view.

{% extends 'layout.html' %}
{% block head %}
    <link rel="stylesheet" href="{{ url_for('static', filename='css/feed.css') }}">
{% endblock %}
{% block title %}Home{% endblock %}
{% block body %}
    <section class="question-feed">
        {% for item in questions %}
            <article class="card">
                <div class="card-avatar">
                    <img src="{{ url_for('static', filename='images/default_avatar.png') }}" alt="avatar">
                </div>
                <div class="card-body">
                    <h3 class="card-title"><a href="{{ url_for('qa.view_question', qid=item.id) }}">{{ item.title }}</a></h3>
                    <p class="card-text">{{ item.content[:100] }}...</p>
                    <div class="meta">
                        <span class="author">{{ item.author.display_name }}</span>
                        <span class="timestamp">{{ item.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
                    </div>
                </div>
            </article>
        {% endfor %}
    </section>
{% endblock %}

The detail page shows the full question, an answer submission form, and a threaded list of answers with user information.

{% extends 'layout.html' %}
{% block head %}
    <link rel="stylesheet" href="{{ url_for('static', filename='css/discussion.css') }}">
{% endblock %}
{% block title %}{{ question.title }} - Details{% endblock %}
{% block body %}
    <article>
        <h2 class="page-heading">{{ question.title }}</h2>
        <div class="meta-info">
            <span>Posted by {{ question.author.display_name }}</span>
            <span>on {{ question.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
        </div>
        <hr>
        <div class="question-body">{{ question.content }}</div>
        <hr>
        <h4>Answers ({{ question.comments|length }})</h4>
        <form action="{{ url_for('qa.add_comment') }}" method="post">
            <input type="hidden" name="qid" value="{{ question.id }}">
            <div class="form-wrapper">
                <div class="mb-3">
                    <input type="text" name="reply" class="form-input" placeholder="Write your answer..." required>
                </div>
                <div class="mb-3">
                    <button type="submit" class="btn-primary">Submit Answer</button>
                </div>
            </div>
        </form>
        <ul class="answer-list">
            {% for ans in question.comments %}
                <li class="answer-item">
                    <div class="user-block">
                        <img class="avatar" src="{{ url_for('static', filename='images/default_avatar.png') }}" alt="">
                        <span class="username">{{ ans.author.display_name }}</span>
                        <span class="date">{{ ans.created_at.strftime('%Y-%m-%d %H:%M') }}</span>
                    </div>
                    <p class="answer-text">{{ ans.content }}</p>
                </li>
            {% endfor %}
        </ul>
    </article>
{% endblock %}

Related Articles

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Comprehensive Guide to Hive SQL Syntax and Operations

This article provides a detailed walkthrough of Hive SQL, categorizing its features and syntax for practical use. Hive SQL is segmented into the following categories: DDL Statements: Operations on...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.