Compare commits

..

4 Commits

Author SHA1 Message Date
6de2588f14 Correct typo 'alter' to 'alert'
Signed-off-by: Vivek Santayana <viveksantayana@noreply.localhost>
2021-11-19 10:05:43 +00:00
011b4e06dd Bug fixes for dynamic meta tags 2021-11-03 12:37:18 +00:00
7747b2a402 Added Twitter title 2021-11-03 11:16:27 +00:00
f0286d7013 v3.0.1 update: Gunicorn and og meta tags 2021-11-03 10:59:14 +00:00
16 changed files with 137 additions and 44 deletions

View File

@ -2,4 +2,4 @@ FROM python:3.10-alpine
WORKDIR /app WORKDIR /app
COPY . . COPY . .
RUN pip install -r requirements.txt RUN pip install -r requirements.txt
CMD ["python","app.py"] CMD ["gunicorn", "-b", "0.0.0.0:5000", "app:app"]

View File

@ -51,10 +51,17 @@ This can be changed by amending the `docker-compose.yml` entry on `line 8` to:
## Version History ## Version History
### Current Version: 3.0.0 (1 November 2021) ### Current Version: 3.0.1 (3 November 2021)
### Changelog ### Changelog
- Serving the web app using Gunicorn so it can handle multiple requests.
- Added OpenGraph and other social media meta tags to allow for sharing on social media.
### Past Versions
#### Version 3.0.0
- Re-built the quiz to run on the server side rather than on the client. - Re-built the quiz to run on the server side rather than on the client.
- Built using Python, Flask, Jinja, and html primarily. - Built using Python, Flask, Jinja, and html primarily.
- Changed the layout to a cleaner, more accessible and modern style using the Bootstrap CSS framework, - Changed the layout to a cleaner, more accessible and modern style using the Bootstrap CSS framework,
@ -62,8 +69,6 @@ This can be changed by amending the `docker-compose.yml` entry on `line 8` to:
- Refined the algorithm by which the Joined duplicates another playbook based on the second-highest scoring match rather than selecting a playbook entirely at random. - Refined the algorithm by which the Joined duplicates another playbook based on the second-highest scoring match rather than selecting a playbook entirely at random.
- Dockerised the quiz and hosting it on my web server, serving it via the front-end reverse proxy. - Dockerised the quiz and hosting it on my web server, serving it via the front-end reverse proxy.
### Past Versions
Because I only started using Git relatively recently, I have not uploaded the code from the older versions onto the repo. Because I only started using Git relatively recently, I have not uploaded the code from the older versions onto the repo.
The repo starts with version 3. The repo starts with version 3.

View File

@ -1,7 +1,12 @@
from flask import render_template, Blueprint, request from flask import render_template, Blueprint, request
from .site_info import site_meta, page_info
error_handler = Blueprint('error_handlers', __name__) error_handler = Blueprint('error_handlers', __name__)
@error_handler.app_errorhandler(404) @error_handler.app_errorhandler(404)
def not_found(e): def not_found(e):
return render_template("404.html") return render_template(
"404.html",
site_meta = site_meta,
page_info = page_info['404']
)

40
interface/site_info.py Normal file
View File

@ -0,0 +1,40 @@
site_meta = {
'title': 'Which Masks Playbook Are You? &mdash; a Personality Quiz',
'name': 'Masks Personality Quiz | V.S.',
'description': 'A personality quiz to find out which Masks playbook you are.',
'locale': 'en_GB',
'theme_colour': '#343a40'
}
page_info = {
'home': {
'url': '/',
'template': 'home.html',
'title': 'Home'
},
'compatibility': {
'url': '/compatibility',
'template': 'compatibility.html',
'title': 'Compatibility and Accessibility'
},
'masks': {
'url': '/masks',
'template': 'masks.html',
'title': 'About Masks'
},
'quiz': {
'url': '/quiz',
'template': 'quiz.html',
'title': 'Take the Quiz'
},
'results': {
'url': '/results',
'template': 'results.html',
'title': 'Your Results'
},
'404': {
'url': '/',
'template': '404.html',
'title': 'Page Not Found'
},
}

View File

@ -1,11 +1,28 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %} Page Not Found {% endblock %} {% block meta %}
<meta name="description" content="{{ site_meta['description']|safe }}" />
<meta property="og:locale" content="{{ site_meta['locale']|safe }}" />
<meta property="og:type" content="website" />
<meta property="og:description" content="{{ site_meta['description']|safe }}" />
<meta property="og:site_name" content="{{ site_meta['name']|safe }}" />
<meta property="og:title" content="{{ page_info['title'] }} | {{ site_meta['name']|safe }}" />
<meta property="og:image" content="https://masks.vsnt.uk{{ url_for('static', filename='favicon.png') }}" />
<meta property="og:image:alt" content="Logo of the Masks Personality Quiz" />
<meta property="og:image:width" content="1024" />
<meta property="og:image:height" content="1024" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="{{ page_info['title'] }} | {{ site_meta['name']|safe }}" />
<meta name="twitter:description" content="{{ site_meta['description']|safe }}" />
<meta name="twitter:image" content="https://masks.vsnt.uk{{ url_for('static', filename='favicon.png') }}" />
<meta name="twitter:image:alt" content="Logo of the Masks Personality Quiz" />
<meta name="twitter:creator" content="@viveksantayana" />
<meta name="twitter:site" content="@viveksantayana" />
<meta name="theme-color" content="#343a40" />
{% endblock %}
{% block content %} {% block content %}
<h1>Error: Page Not Found</h1> <h1>Error: Page Not Found</h1>
<div class="container"> <div class="container">
<a href="/">Return to the Home Page</a> <a href="/">Return to the Home Page</a>
</div> </div>
{% endblock %} {% endblock %}

View File

@ -23,8 +23,11 @@
<link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}"> <link rel="shortcut icon" href="{{ url_for('static', filename='favicon.ico') }}">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='favicon.png') }}"> <link rel="apple-touch-icon" href="{{ url_for('static', filename='favicon.png') }}">
<title> <title>
{% block title %}{% endblock %} | Which Masks Playbook Are You? &mdash; a Personality Quiz {{ page_info['title']|safe }} | {{ site_meta['title']|safe }}
</title> </title>
{% block meta %}
{% include "og-meta.html" %}
{% endblock %}
</head> </head>
<body> <body>
@ -48,7 +51,7 @@
{% if messages %} {% if messages %}
{% for category, message in messages %} {% for category, message in messages %}
{% if category == 'error' %} {% if category == 'error' %}
<div class="alert alert-danger alter-dismissable fade show" role="alert"> <div class="alert alert-danger alert-dismissable fade show" role="alert">
<div class="container"> <div class="container">
<svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#exclamation-triangle-fill"/></svg> <svg class="bi flex-shrink-0 me-2" width="24" height="24" role="img" aria-label="Danger:"><use xlink:href="#exclamation-triangle-fill"/></svg>
{{ message|safe }} {{ message|safe }}

View File

@ -1,5 +1,4 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %} Compatibility and Accessibility {% endblock %}
{% block content %} {% block content %}
<h1>Compatibility and Accessibility</h1> <h1>Compatibility and Accessibility</h1>

View File

@ -1,23 +0,0 @@
{% extends "base.html" %}
{% block title %} Development {% endblock %}
{% block content %}
<h1>Development</h1>
<p>
I started work on the first version of this personality quiz over a year ago, when I decided to teach myself programming. The original version ran on JavaScript and was rendered and evaluated entirely by the browser. This version runs entirely on the server, and the quiz results are evaluated by the server rather than the client.
</p>
<p>
The back-end runs on Python 3.10, using the Flask framework. The web pages are templated using Jinja. The web site is rendered using html and the Bootstrap CSS framework. There have been some elements of the interface enhanced using rudimentary JavaScript, including the jQuery library. Of all of these programming languages, Python has proved to be a far more intuitive language to learn, and generally programming this has been a lot more enjoyable and a lot less frustrating than the earlier iteration that used JavaScript.
</p>
<p>
The quiz runs by rendering a form with all of the questions and options on the browser. The browser then submits the responses via a POST request to the server. The server evaluates the responses and renders the results accordingly. There is no information stored on the client.
</p>
<p>
All data transacted between the client and the server is stored as a Flask session for the duration that the user is on the web site. If the user closes the site, the session data is deleted and all answers submitted are lost.
</p>
<p>
There are, of course, some considerable inefficiencies in the way I have set up the templating. I have written the text for all the various web pages in the templates of the respective pages. Ideally, I would have preferred having a single template for all of the web pages or views, and then have the content served on those pages render dynamically based on the URL query. But that was a level of programming that was gratuitous at this point, as really most of the pages were serving static content anyway so it did not matter just now.
</p>
<p>
In addition, this is the first time I am storing the code of the quiz <a href="" title="Link to the code on my Git Repo">on my Git repo</a>, and having a much more streamlined version control process.
</p>
{% endblock %}

View File

@ -1,7 +1,6 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %} Home | Which Masks Playbook Are You? &mdash; a Personality Quiz {% endblock %}
{% block content %} {% block content %}
<h1>Home page</h1> <h1>Home Page</h1>
<h3 class="section-head">Background</h3> <h3 class="section-head">Background</h3>

View File

@ -1,5 +1,4 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %} About Masks {% endblock %}
{% block content %} {% block content %}
<h1>About <em>Masks</em></h1> <h1>About <em>Masks</em></h1>
<p><em>Masks</em> is a game by Brendan Conway using the Powered by the Apocalypse engine. For those unfamiliar with the term, Powered by the Apocalypse is a category of game systems that are based on the overall structure of the <em>Apocalypse World</em> system by Vincent and Meguey Baker. The core concept behind this system is to encourage collaborate and improvisational story-telling by streamlining the mechanics. This is in sharp contrast to traditional TRPGs that have very granular rules and hierarchical authority between a Game Master and the players.</p> <p><em>Masks</em> is a game by Brendan Conway using the Powered by the Apocalypse engine. For those unfamiliar with the term, Powered by the Apocalypse is a category of game systems that are based on the overall structure of the <em>Apocalypse World</em> system by Vincent and Meguey Baker. The core concept behind this system is to encourage collaborate and improvisational story-telling by streamlining the mechanics. This is in sharp contrast to traditional TRPGs that have very granular rules and hierarchical authority between a Game Master and the players.</p>

View File

@ -0,0 +1,19 @@
<meta name="description" content="{{ site_meta['description']|safe }}" />
<meta property="og:locale" content="{{ site_meta['locale']|safe }}" />
<meta property="og:type" content="website" />
<meta property="og:description" content="{{ site_meta['description']|safe }}" />
<meta property="og:url" content="https://masks.vsnt.uk{{ url_for(request.endpoint, **request.view_args) }}" />
<meta property="og:site_name" content="{{ site_meta['name']|safe }}" />
<meta property="og:title" content="{{ page_info['title'] }} | {{ site_meta['title']|safe }}" />
<meta property="og:image" content="https://masks.vsnt.uk{{ url_for('static', filename='favicon.png') }}" />
<meta property="og:image:alt" content="Logo of the Masks Personality Quiz" />
<meta property="og:image:width" content="1024" />
<meta property="og:image:height" content="1024" />
<meta name="twitter:card" content="summary" />
<meta name="twitter:title" content="{{ page_info['title'] }} | {{ site_meta['title']|safe }}" />
<meta name="twitter:description" content="{{ site_meta['description']|safe }}" />
<meta name="twitter:image" content="https://masks.vsnt.uk{{ url_for('static', filename='favicon.png') }}" />
<meta name="twitter:image:alt" content="Logo of the Masks Personality Quiz" />
<meta name="twitter:creator" content="@viveksantayana" />
<meta name="twitter:site" content="@viveksantayana" />
<meta name="theme-color" content="#343a40" />

View File

@ -1,5 +1,4 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %} Take the Quiz {% endblock %}
{% block content %} {% block content %}
<h1>Take the Quiz</h1> <h1>Take the Quiz</h1>

View File

@ -1,5 +1,4 @@
{% extends "base.html" %} {% extends "base.html" %}
{% block title %} Your Results {% endblock %}
{% block content %} {% block content %}
<h1>Your Results</h1> <h1>Your Results</h1>
<a name="top-anchor"></a> <a name="top-anchor"></a>

View File

@ -6,20 +6,33 @@ from data.questions import questions
from data.sources import sources from data.sources import sources
from quiz.validators import validate_submissions from quiz.validators import validate_submissions
from quiz.evaluation import evaluate_quiz from quiz.evaluation import evaluate_quiz
from .site_info import site_meta, page_info
views = Blueprint('views', __name__) views = Blueprint('views', __name__)
@views.route('/compatibility') @views.route('/compatibility')
def compatibility(): def compatibility():
return render_template('compatibility.html') return render_template(
'compatibility.html',
site_meta = site_meta,
page_info = page_info['compatibility']
)
@views.route('/') @views.route('/')
def home(): def home():
return render_template('home.html') return render_template(
'home.html',
site_meta = site_meta,
page_info = page_info['home']
)
@views.route('/masks') @views.route('/masks')
def about(): def about():
return render_template('masks.html') return render_template(
'masks.html',
site_meta = site_meta,
page_info = page_info['masks']
)
@views.route('/quiz', methods=['GET', 'POST']) @views.route('/quiz', methods=['GET', 'POST'])
def quiz(): def quiz():
@ -27,14 +40,28 @@ def quiz():
session['submission'] = request.form session['submission'] = request.form
if validate_submissions(session['submission']): if validate_submissions(session['submission']):
return redirect(url_for('views.results')) return redirect(url_for('views.results'))
return render_template('quiz.html', questions=questions, sources=sources) return render_template(
'quiz.html',
site_meta = site_meta,
page_info = page_info['quiz'],
questions=questions,
sources=sources
)
@views.route('/results') @views.route('/results')
def results(): def results():
if 'submission' not in session: if 'submission' not in session:
return redirect(url_for('views.quiz')) return redirect(url_for('views.quiz'))
results = evaluate_quiz(session['submission']) results = evaluate_quiz(session['submission'])
return render_template('results.html', results = results, playbooks = playbooks, labels = labels, sources = sources) return render_template(
'results.html',
site_meta = site_meta,
page_info = page_info['results'],
results = results,
playbooks = playbooks,
labels = labels,
sources = sources
)
@views.route('/reset') @views.route('/reset')
def reset(): def reset():

View File

@ -1,6 +1,7 @@
click==8.0.3 click==8.0.3
colorama==0.4.4 colorama==0.4.4
Flask==2.0.2 Flask==2.0.2
gunicorn==20.1.0
itsdangerous==2.0.1 itsdangerous==2.0.1
Jinja2==3.0.2 Jinja2==3.0.2
MarkupSafe==2.0.1 MarkupSafe==2.0.1

4
wsgi.py Normal file
View File

@ -0,0 +1,4 @@
from app import app
if __name__ == '__main__':
app.run()