Crafting Frontend Interfaces with Jinja2 for a Flask Q&A Site
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 %}