From 772943acb09db64743bd3c3c30a26749d768b3f3 Mon Sep 17 00:00:00 2001
From: Raoul Snyman <raoul@snyman.info>
Date: Tue, 26 Jan 2021 22:38:37 -0700
Subject: [PATCH] Drag this up into the current technology

---
 .gitignore                                    |   3 +
 setup.py                                      |  49 +++++++
 stickynotes.cfg                               |   2 +-
 stickynotes/__init__.py                       |  33 +++--
 stickynotes/db.py                             |  15 ++
 stickynotes/models.py                         |  32 +----
 stickynotes/static/custom.css                 |  31 ++++-
 .../templates/{about.mako => about.html}      |   4 +-
 stickynotes/templates/base.html               |  51 +++++++
 stickynotes/templates/base.mako               |  71 ----------
 .../templates/{index.mako => index.html}      |  37 +++--
 stickynotes/templates/raw.html                |   1 +
 stickynotes/templates/raw.mako                |   1 -
 stickynotes/templates/view.html               |  16 +++
 stickynotes/templates/view.mako               |   9 --
 stickynotes/views.py                          | 129 +++++++++---------
 wsgiapp.py => wsgi.py                         |   3 +-
 17 files changed, 273 insertions(+), 214 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 setup.py
 create mode 100644 stickynotes/db.py
 rename stickynotes/templates/{about.mako => about.html} (81%)
 create mode 100644 stickynotes/templates/base.html
 delete mode 100644 stickynotes/templates/base.mako
 rename stickynotes/templates/{index.mako => index.html} (62%)
 create mode 100644 stickynotes/templates/raw.html
 delete mode 100644 stickynotes/templates/raw.mako
 create mode 100644 stickynotes/templates/view.html
 delete mode 100644 stickynotes/templates/view.mako
 rename wsgiapp.py => wsgi.py (70%)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cf1b9d7
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,3 @@
+__pycache__
+*.egg-info
+*.sqlite
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..506840a
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,49 @@
+from setuptools import setup
+
+
+setup(
+    name='StickyNotes',
+    version='0.2',
+    author='Raoul Snyman',
+    description='A simple pastebin',
+    url='https://bin.snyman.info',
+    license='GPLv3+',
+    packages=['stickynotes'],
+    include_package_data=True,
+    platforms='any',
+    python_requires='>=3.5',
+    install_requires=[
+        'Flask',
+        'Flask-SQLAlchemy',
+        'Pygments',
+        'requests',
+        'short_url'
+    ],
+    extras_require={
+        'dev': [
+            'pytest>=3',
+            'pytest-cov',
+        ],
+    },
+    classifiers=[
+        'Development Status :: 2 - Pre-Alpha',
+        'Environment :: Web Environment',
+        'Framework :: Flask',
+        'Intended Audience :: Other Audience',
+        'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
+        'Natural Language :: English',
+        'Operating System :: OS Independent',
+        'Programming Language :: Python',
+        'Programming Language :: Python :: 3',
+        'Programming Language :: Python :: 3.5',
+        'Programming Language :: Python :: 3.6',
+        'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
+        'Programming Language :: Python :: 3 :: Only',
+        'Topic :: Internet :: WWW/HTTP',
+        'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+        'Topic :: Internet :: WWW/HTTP :: Dynamic Content :: Content Management System',
+        'Topic :: Internet :: WWW/HTTP :: WSGI',
+        'Topic :: Internet :: WWW/HTTP :: WSGI :: Application',
+    ],
+)
diff --git a/stickynotes.cfg b/stickynotes.cfg
index 2c7ebb6..89a739f 100644
--- a/stickynotes.cfg
+++ b/stickynotes.cfg
@@ -1,5 +1,5 @@
 [stickynotes]
-database_url = sqlite:///stickynotes.sqlite
+sqlalchemy_database_uri = sqlite:///stickynotes.sqlite
 secret_key = yoursecretkeyhere
 recaptcha_site_key = 
 recaptcha_secret_key = 
diff --git a/stickynotes/__init__.py b/stickynotes/__init__.py
index e79ee85..2c3124e 100644
--- a/stickynotes/__init__.py
+++ b/stickynotes/__init__.py
@@ -2,39 +2,42 @@
 """
 StickyNotes, yet another paste bin
 """
-import os
-from ConfigParser import SafeConfigParser
+from configparser import ConfigParser
+from pathlib import Path
 
 from flask import Flask
-from flask.ext.mako import MakoTemplates
 
-from models import init_db
-from views import views
+from stickynotes.db import db
+from stickynotes.views import views
 
 
-def read_config():
+def read_config(config_path=None):
     """
     Read the configuration file and return the values in a dictionary
     """
-    config_file = os.path.abspath(os.path.join(os.path.dirname(__file__), u'..', u'stickynotes.cfg'))
-    config_parser = SafeConfigParser()
+    if config_path:
+        config_file = config_path / 'stickynotes.cfg'
+    else:
+        config_file = Path(__file__).parent / '..' / 'stickynotes.cfg'
+    config_parser = ConfigParser()
     config_parser.read(config_file)
     config = {}
-    for option in config_parser.options(u'stickynotes'):
-        config[option.upper()] = config_parser.get(u'stickynotes', option)
-    print(config)
+    for option in config_parser.options('stickynotes'):
+        config[option.upper()] = config_parser.get('stickynotes', option)
     return config
 
 
-def make_app():
+def make_app(config_path=None):
     """
     Create the application object
     """
     app = Flask(__name__)
     # Load the config file
-    config = read_config()
+    config = read_config(config_path)
     app.config.update(config)
-    MakoTemplates(app)
-    init_db(config[u'DATABASE_URL'])
+    app.config.update({'SQLALCHEMY_TRACK_MODIFICATIONS': False})
+    db.init_app(app)
+    with app.app_context():
+        db.create_all()
     app.register_blueprint(views)
     return app
diff --git a/stickynotes/db.py b/stickynotes/db.py
new file mode 100644
index 0000000..dc6f272
--- /dev/null
+++ b/stickynotes/db.py
@@ -0,0 +1,15 @@
+# -*- coding: utf-8 -*-
+"""
+The basics of the database
+"""
+from flask_sqlalchemy import SQLAlchemy
+
+db = SQLAlchemy()
+session = db.session
+Model = db.Model
+Column = db.Column
+Integer = db.Integer
+String = db.String
+Text = db.String
+DateTime = db.DateTime
+Boolean = db.Boolean
diff --git a/stickynotes/models.py b/stickynotes/models.py
index 9b07bef..4e55d8c 100644
--- a/stickynotes/models.py
+++ b/stickynotes/models.py
@@ -4,44 +4,20 @@ The models in use
 """
 from datetime import datetime
 
-from sqlalchemy import Column, Integer, String, Text, DateTime, create_engine, Boolean
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import sessionmaker, scoped_session
-
-BaseModel = declarative_base()
-db_session = None
+from stickynotes.db import Model, Column, Integer, String, Text, DateTime, Boolean
 
 
-class StickyNote(BaseModel):
+class StickyNote(Model):
     """
     The main (only?) table in the system
     """
-    __tablename__ = u'sticky_notes'
+    __tablename__ = 'sticky_notes'
 
     id = Column(Integer, autoincrement=True, primary_key=True)
     title = Column(String(255))
     source = Column(Text)
-    lexer = Column(String(255), default=u'text')
+    lexer = Column(String(255), default='text')
     created = Column(DateTime, default=datetime.now, index=True)
     expiry = Column(DateTime, default=None, index=True)
     url = Column(String(255), index=True)
     private = Column(Boolean, default=False, index=True)
-
-
-def init_db(database_url):
-    """
-    Initialise the database connection
-
-    :param database_url: The database connection URL
-    """
-    global db_session
-    engine = create_engine(database_url, pool_recycle=3600)
-    db_session = scoped_session(sessionmaker(bind=engine))()
-    BaseModel.metadata.create_all(engine, checkfirst=True)
-
-
-def get_session():
-    """
-    Get the current database session
-    """
-    return db_session
diff --git a/stickynotes/static/custom.css b/stickynotes/static/custom.css
index d4bbeaf..b370cca 100644
--- a/stickynotes/static/custom.css
+++ b/stickynotes/static/custom.css
@@ -8,7 +8,7 @@ body {
 }
 
 body > .container {
-  padding: 70px 15px 0;
+  padding: 1rem 0;
 }
 
 .container .text-muted {
@@ -27,18 +27,41 @@ body > .container {
   padding-left: 15px;
 }
 
-code {
-  font-size: 80%;
+code, kbd, pre, samp {
+  font-family: 'PT Mono', 'Hack', SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
 }
 
 .sourcetable {
-    width: 100%;
+  width: 100%;
 }
 
 .linenos {
     text-align: right;
 }
 
+td.linenos pre {
+  color: var(--light);
+  background-color: var(--secondary);
+}
+
+.source {
+  color: var(--light);
+  background-color: var(--dark);
+}
+
 .note-links {
     margin-bottom: 10px;
 }
+
+.form-control,
+.form-control:focus {
+  background-color: var(--dark);
+}
+
+.form-control {
+  color: var(--light);
+}
+
+.form-control:focus {
+  color: var(--white);
+}
diff --git a/stickynotes/templates/about.mako b/stickynotes/templates/about.html
similarity index 81%
rename from stickynotes/templates/about.mako
rename to stickynotes/templates/about.html
index 5ab1fd7..badad55 100644
--- a/stickynotes/templates/about.mako
+++ b/stickynotes/templates/about.html
@@ -1,7 +1,9 @@
-<%inherit file="base.mako"/>
+{% extends "base.html" %}
+        {% block content %}
         <div class="row">
             <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
                 <h2>About StickyNotes</h2>
                 <p>StickyNotes is a quick code paste application written in Python with Flask, SQLAlchemy, Mako, Pygments and a few other Python libraries.</p>
             </div>
         </div>
+        {% endblock %}
diff --git a/stickynotes/templates/base.html b/stickynotes/templates/base.html
new file mode 100644
index 0000000..3d02ef9
--- /dev/null
+++ b/stickynotes/templates/base.html
@@ -0,0 +1,51 @@
+<!DOCTYPE html>
+<html>
+  <head lang="en">
+    <meta charset="utf-8">
+    <meta http-equiv="X-UA-Compatible" content="IE=edge">
+    <meta name="viewport" content="width=device-width, initial-scale=1">
+    <title>StickyNotes</title>
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootswatch@4.6.0/dist/darkly/bootstrap.min.css" type="text/css">
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-select@1.13.14/dist/css/bootstrap-select.min.css" type="text/css">
+    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" type="text/css">
+    <link rel="preconnect" href="https://fonts.gstatic.com">
+    <link href="https://fonts.googleapis.com/css2?family=PT+Mono&display=swap" rel="stylesheet"> 
+    <link href="/pygments.css" rel="stylesheet" type="text/css">
+    <link href="/static/custom.css" rel="stylesheet" type="text/css">
+  </head>
+  <body>
+    <nav class="navbar navbar-expand-lg navbar-dark bg-primary navbar-fixed-top">
+      <div class="container">
+        <a href="/" class="navbar-brand">StickyNotes</a>
+        <button type="button" class="navbar-toggler collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
+          <span class="navbar-toggler-icon"></span>
+        </button>
+        <div id="navbar" class="collapse navbar-collapse">
+          <ul class="navbar-nav mr-auto">
+            <li class="nav-item{% if request.path == '/' %} active{% endif %}"><a class="nav-link" href="/">New</a></li>
+            {# <li class="nav-item{% if request.path == '/notes' %} active{% endif %}"><a class="nav-link" href="/notes">Notes</a></li> #}
+            <li class="nav-item{% if request.path == '/about' %} active{% endif %}"><a class="nav-link" href="/about">About</a></li>
+          </ul>
+        </div>
+      </div>
+    </nav>
+    <div class="container">
+      {% for category, message in get_flashed_messages(True) %}
+      <div class="alert alert-{{category}}" role="alert">
+        {{message}}
+      </div>
+      {% endfor %}
+      {% block content %}
+      {% endblock %}
+    </div>
+    <footer class="footer">
+      <div class="container">
+        <p class="text-muted">Copyright &copy; 2015 Raoul Snyman.</p>
+      </div>
+    </footer>
+    <script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap@4.6.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-Piv4xVNRyMGpqkS2by6br4gNJ7DXjqk09RmUpJ8jgGtD7zP9yug3goQfGII0yAns" crossorigin="anonymous"></script>
+    <script src="https://cdn.jsdelivr.net/npm/bootstrap-select@1.13.14/dist/js/bootstrap-select.min.js"></script>
+    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
+  </body>
+</html>
diff --git a/stickynotes/templates/base.mako b/stickynotes/templates/base.mako
deleted file mode 100644
index c2bfdad..0000000
--- a/stickynotes/templates/base.mako
+++ /dev/null
@@ -1,71 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head lang="en">
-    <meta charset="utf-8">
-    <meta http-equiv="X-UA-Compatible" content="IE=edge">
-    <meta name="viewport" content="width=device-width, initial-scale=1">
-    <title>StickyNotes</title>
-    <link href="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.3/css/bootstrap-select.min.css" rel="stylesheet" type="text/css">
-    <link href="//maxcdn.bootstrapcdn.com/bootswatch/3.3.6/lumen/bootstrap.min.css" rel="stylesheet" type="text/css">
-    <link href="//maxcdn.bootstrapcdn.com/font-awesome/4.5.0/css/font-awesome.min.css" rel="stylesheet">
-    <link href="/static/custom.css" rel="stylesheet" type="text/css">
-    <link href="/pygments.css" rel="stylesheet" type="text/css">
-</head>
-<body>
-    <nav class="navbar navbar-default navbar-fixed-top">
-        <div class="container">
-            <div class="navbar-header">
-                <button type="button" class="navbar-toggle collapsed" data-toggle="collapse" data-target="#navbar" aria-expanded="false" aria-controls="navbar">
-                    <span class="sr-only">Toggle navigation</span>
-                    <span class="icon-bar"></span>
-                    <span class="icon-bar"></span>
-                    <span class="icon-bar"></span>
-                </button>
-                <a href="/" class="navbar-brand">StickyNotes</a>
-            </div>
-            <div id="navbar" class="collapse navbar-collapse">
-                <ul class="nav navbar-nav">
-% if self.name == u'self:index.mako':
-                    <li class="active"><a href="/">New</a></li>
-%else:
-                    <li><a href="/">New</a></li>
-% endif
-<%doc>
-% if self.name == u'self:list.mako':
-                    <li class="active"><a href="/notes">Notes</a></li>
-%else:
-                    <li><a href="/notes">Notes</a></li>
-% endif
-</%doc>
-% if self.name == u'self:about.mako':
-                    <li class="active"><a href="/about">About</a></li>
-%else:
-                    <li><a href="/about">About</a></li>
-% endif
-                </ul>
-            </div>
-        </div>
-    </nav>
-    <div class="container">
-<% messages = get_flashed_messages(True) %>
-% if messages:
-    % for category, message in messages:
-        <div class="alert alert-${category}" role="alert">
-            ${message}
-        </div>
-    % endfor
-% endif
-        ${self.body()}
-    </div>
-    <footer class="footer">
-        <div class="container">
-            <p class="text-muted">Copyright &copy; 2015 Raoul Snyman.</p>
-        </div>
-    </footer>
-    <script src="//code.jquery.com/jquery-1.11.3.min.js"></script>
-    <script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
-    <script src="//maxcdn.bootstrapcdn.com/bootstrap/3.3.2/js/bootstrap.min.js" type="application/javascript"></script>
-    <script src="//cdnjs.cloudflare.com/ajax/libs/bootstrap-select/1.9.3/js/bootstrap-select.min.js"></script>
-    <script src="https://www.google.com/recaptcha/api.js" async defer></script>
-</body>
-</html>
diff --git a/stickynotes/templates/index.mako b/stickynotes/templates/index.html
similarity index 62%
rename from stickynotes/templates/index.mako
rename to stickynotes/templates/index.html
index a240eea..1a1a9ac 100644
--- a/stickynotes/templates/index.mako
+++ b/stickynotes/templates/index.html
@@ -1,7 +1,8 @@
-<%inherit file="base.mako"/>
+{% extends "base.html" %}
+        {% block content %}
         <div class="row">
             <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
-                <form role="form" action="/save" method="post" form>
+                <form role="form" action="/" method="post">
                     <div class="form-group">
                         <textarea name="source" id="source" class="form-control" rows="20"></textarea>
                     </div>
@@ -10,19 +11,15 @@
                     </div>
                     <div class="form-group">
                         <label for="language">Language</label>
-                        <select name="language" id="language" class="form-control selectpicker" data-live-search="true" data-size="15">
-% for language in lexers:
-    % if language[0] == 'text':
-                            <option value="${language[0]}" selected>${language[1]}</option>
-    % else:
-                            <option value="${language[0]}">${language[1]}</option>
-    % endif
-% endfor
+                        <select name="language" id="language" class="form-control selectpicker" data-live-search="true" data-size="15" data-style="btn-default">
+                          {% for language in lexers %}
+                          <option value="{{language[0]}}"{% if language[0] == 'text' %} selected{% endif %}>{{language[1]}}</option>
+                          {% endfor %}
                         </select>
                     </div>
                     <div class="form-group">
                         <label for="expiry">Expiry</label>
-                        <select name="expiry" id="expiry" class="form-control selectpicker">
+                        <select name="expiry" id="expiry" class="form-control selectpicker" data-style="btn-default">
                             <option value="never" selected>Never</option>
                             <option value="10min">10 Minutes</option>
                             <option value="1h">1 Hour</option>
@@ -32,17 +29,19 @@
                             <option value="1m">1 Month</option>
                         </select>
                     </div>
-                    <div class="checkbox">
-                        <label>
-                            <input type="checkbox" name="private"> Unlisted (doesn't appear in the list on the notes page)
-                        </label>
-                    </div>
-% if recaptcha_site_key:
                     <div class="form-group">
-                        <div class="g-recaptcha" data-sitekey="${recaptcha_site_key}"></div>
+                      <div class="custom-control custom-checkbox">
+                        <input type="checkbox" name="private" class="custom-control-input" id="private">
+                        <label for="private" class="custom-control-label">Unlisted (doesn't appear in the list on the notes page)</label>
+                      </div>
                     </div>
-% endif
+                    {% if recaptcha_site_key %}
+                    <div class="form-group">
+                        <div class="g-recaptcha" data-sitekey="{{recaptcha_site_key}}"></div>
+                    </div>
+                    {% endif %}
                     <button type="submit" class="btn btn-primary">Submit</button>
                 </form>
             </div>
         </div>
+        {% endblock %}
diff --git a/stickynotes/templates/raw.html b/stickynotes/templates/raw.html
new file mode 100644
index 0000000..3a73287
--- /dev/null
+++ b/stickynotes/templates/raw.html
@@ -0,0 +1 @@
+{{source}}
diff --git a/stickynotes/templates/raw.mako b/stickynotes/templates/raw.mako
deleted file mode 100644
index fc64b76..0000000
--- a/stickynotes/templates/raw.mako
+++ /dev/null
@@ -1 +0,0 @@
-${source}
diff --git a/stickynotes/templates/view.html b/stickynotes/templates/view.html
new file mode 100644
index 0000000..39d6183
--- /dev/null
+++ b/stickynotes/templates/view.html
@@ -0,0 +1,16 @@
+{% extends "base.html" %}
+        {% block content %}
+        <div class="row">
+          <div class="col-md-10 col-sm-12">
+            <h4>{{note.title}}</h4>
+          </div>
+          <div class="col-md-2 col-sm-12 text-right">
+            <a href="/raw/{{note.url}}" class="btn btn-secondary btn-sm"><i class="fa fa-fw fa-file-text"></i> Raw</a>
+          </div>
+        </div>
+        <div class="row">
+          <div class="col-12">
+            {{source | safe}}
+          </div>
+        </div>
+        {% endblock %}
diff --git a/stickynotes/templates/view.mako b/stickynotes/templates/view.mako
deleted file mode 100644
index d773c6f..0000000
--- a/stickynotes/templates/view.mako
+++ /dev/null
@@ -1,9 +0,0 @@
-<%inherit file="base.mako"/>
-        <div class="row">
-            <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12 text-right note-links">
-                <a href="/raw/${note.url}" class="btn btn-default btn-sm"><i class="fa fa-fw fa-file-text"></i> Raw</a>
-            </div>
-            <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
-                ${source}
-            </div>
-        </div>
diff --git a/stickynotes/views.py b/stickynotes/views.py
index bb0689f..f5a41b3 100644
--- a/stickynotes/views.py
+++ b/stickynotes/views.py
@@ -2,29 +2,31 @@
 """
 The views
 """
-from datetime import timedelta, datetime
 import logging
+from datetime import timedelta, datetime
 
-from flask import Blueprint, redirect, request, flash, make_response, current_app
-from flask.ext.mako import render_template
+import requests
+import short_url
+from flask import Blueprint, redirect, request, flash, make_response, current_app, render_template
 from pygments import highlight
 from pygments.formatters.html import HtmlFormatter
 from pygments.lexers import get_lexer_by_name, get_all_lexers
 from sqlalchemy import or_
-import requests
-import short_url
 
-from models import StickyNote, get_session
+from stickynotes.db import session
+from stickynotes.models import StickyNote
+
 
 log = logging.getLogger(__name__)
-views = Blueprint(u'views', __name__)
+views = Blueprint('views', __name__)
+
 
 EXPIRY_DELTAS = {
-    u'10min': timedelta(minutes=10),
-    u'1d': timedelta(days=1),
-    u'1w': timedelta(days=7),
-    u'2w': timedelta(days=14),
-    u'1m': timedelta(days=30)
+    '10min': timedelta(minutes=10),
+    '1d': timedelta(days=1),
+    '1w': timedelta(days=7),
+    '2w': timedelta(days=14),
+    '1m': timedelta(days=30)
 }
 
 
@@ -32,79 +34,78 @@ def _is_recaptcha_valid(secret, response, remote_ip=None):
     """
     POST to the recaptcha service to check if the recaptcha is valid
     """
-    data = {u'secret': secret, u'response': response}
+    data = {'secret': secret, 'response': response}
     if remote_ip:
-        data[u'remoteip'] = remote_ip
-    response = requests.post(u'https://www.google.com/recaptcha/api/siteverify', data=data)
+        data['remoteip'] = remote_ip
+    response = requests.post('https://www.google.com/recaptcha/api/siteverify', data=data)
     try:
         json_response = response.json()
-        print(json_response)
-        return json_response[u'success']
+        return json_response['success']
     except ValueError:
-        print response
         return False
 
 
-@views.route('/', methods=[u'GET'])
+@views.route('/', methods=['GET'])
 def index():
     """
     Add a new sticky note
     """
-    all_lexers = [(lexer[1][0], lexer[0]) for lexer in get_all_lexers()]
+    all_lexers = [(lexer[1][0], lexer[0]) for lexer in get_all_lexers() if len(lexer) > 1 and len(lexer[1]) > 0]
     all_lexers.sort(key=lambda x: x[1].lower())
-    now = datetime.utcnow()
-    recaptcha_site_key = current_app.config.get(u'RECAPTCHA_SITE_KEY')
-    return render_template(u'index.mako', lexers=all_lexers, recaptcha_site_key=recaptcha_site_key)
+    recaptcha_site_key = current_app.config.get('RECAPTCHA_SITE_KEY')
+    return render_template('index.html', lexers=all_lexers, recaptcha_site_key=recaptcha_site_key)
 
 
-@views.route('/notes', methods=[u'GET'])
+@views.route('/notes', methods=['GET'])
 def notes():
     """
     Show a list of recent notes
     """
-    recent_notes = get_session().query(StickyNote)\
-        .filter(or_(StickyNote.expiry == None, StickyNote.expiry < now))\
+    recent_notes = StickyNote.query\
+        .filter(or_(StickyNote.expiry == None, StickyNote.expiry < datetime.utcnow()))\
         .filter(~StickyNote.private)\
         .order_by(StickyNote.created.desc())\
-        .limit(10)
-    return render_template(u'notes.mako', recent=recent_notes)
+        .limit(10)                                                                              # noqa
+    return render_template('notes.html', recent=recent_notes)
 
 
-@views.route('/about', methods=[u'GET'])
+@views.route('/about', methods=['GET'])
 def about():
     """
     Show the about page
     """
-    return render_template(u'about.mako')
+    return render_template('about.html')
 
 
-@views.route('/save', methods=[u'POST'])
+@views.route('/', methods=['POST'])
 def save():
     """
     Save a sticky note
     """
     # Check if the recaptcha is valid
-    recaptcha_secret_key = current_app.config.get(u'RECAPTCHA_SECRET_KEY')
-    is_recaptcha_valid = False
-    try:
-        is_recaptcha_valid = _is_recaptcha_valid(recaptcha_secret_key, request.form[u'g-recaptcha-response'])
-    except KeyError:
-        flash(u'Unable to verify you, don\'t forget to complete the captcha.', u'danger')
-        print(u'No form variable')
+    recaptcha_secret_key = current_app.config.get('RECAPTCHA_SECRET_KEY')
+    if recaptcha_secret_key:
+        is_recaptcha_valid = False
+        try:
+            is_recaptcha_valid = _is_recaptcha_valid(recaptcha_secret_key, request.form['g-recaptcha-response'])
+        except KeyError:
+            flash('Unable to verify you, don\'t forget to complete the captcha.', 'danger')
+            print('No form variable')
+    else:
+        is_recaptcha_valid = True
     if not is_recaptcha_valid:
-        return redirect(u'/')
+        return redirect('/')
     # Save the note
-    db_session = get_session()
     try:
         created = datetime.utcnow()
-        expiry = EXPIRY_DELTAS.get(request.form[u'expiry'])
+        expiry = EXPIRY_DELTAS.get(request.form['expiry'])
         if expiry:
             expiry = created + expiry
-        source = request.form[u'source']
-        lexer = request.form[u'language']
-        title = request.form.get(u'title', u'')
-        private = True if request.form.get(u'private') else False
-        string_id = u''.join([source, lexer, title, created.isoformat()])
+        source = request.form['source']
+        lexer = request.form['language']
+        title = request.form.get('title', '')
+        private = True if request.form.get('private') else False
+        string_id = ''.join([source, lexer, title, created.isoformat()])
         url = short_url.encode_url(sum([ord(char) for char in string_id]), min_length=8)
         note = StickyNote(
             title=title,
@@ -115,52 +116,52 @@ def save():
             private=private,
             url=url
         )
-        db_session.add(note)
-        db_session.commit()
-        return redirect(u'/' + note.url)
+        session.add(note)
+        session.commit()
+        return redirect('/' + note.url)
     except Exception as e:
         flash(str(e), 'danger')
-        db_session.rollback()
-        return redirect(u'/')
+        session.rollback()
+        return redirect('/')
 
 
-@views.route('/<string:note_url>', methods=[u'GET'])
+@views.route('/<string:note_url>', methods=['GET'])
 def view(note_url):
     """
     Show a sticky note
 
     :param note_url: The note to show
     """
-    note = get_session().query(StickyNote).filter(StickyNote.url == note_url).scalar()
+    note = StickyNote.query.filter(StickyNote.url == note_url).scalar()
     if not note:
-        flash(u'That note does not exist', 'danger')
-        return redirect(u'/')
+        flash('That note does not exist', 'danger')
+        return redirect('/')
 
     lexer = get_lexer_by_name(note.lexer)
-    formatter = HtmlFormatter(linenos=True, cssclass=u'source')
+    formatter = HtmlFormatter(linenos=True, cssclass='source')
     result = highlight(note.source, lexer, formatter)
-    return render_template(u'view.mako', note=note, source=result)
+    return render_template('view.html', note=note, source=result)
 
 
-@views.route('/raw/<string:note_url>', methods=[u'GET'])
+@views.route('/raw/<string:note_url>', methods=['GET'])
 def raw(note_url):
     """
     Show the raw version of a  sticky note
 
     :param note_url: The note to show
     """
-    note = get_session().query(StickyNote).filter(StickyNote.url == note_url).scalar()
+    note = StickyNote.query.filter(StickyNote.url == note_url).scalar()
     if not note:
-        flash(u'That note does not exist', 'danger')
-        return redirect(u'/')
-    return render_template(u'raw.mako', source=note.source), 200, {'Content-Type': 'text/plain; charset=utf-8'}
+        flash('That note does not exist', 'danger')
+        return redirect('/')
+    return render_template('raw.html', source=note.source), 200, {'Content-Type': 'text/plain; charset=utf-8'}
 
 
-@views.route(u'/pygments.css', methods=[u'GET'])
+@views.route('/pygments.css', methods=['GET'])
 def pygments_css():
     """
     Return the Pygments CSS to the browser
     """
-    response = make_response(HtmlFormatter().get_style_defs())
+    response = make_response(HtmlFormatter(style='nord').get_style_defs())
     response.headers['Content-Type'] = 'text/css'
     return response
diff --git a/wsgiapp.py b/wsgi.py
similarity index 70%
rename from wsgiapp.py
rename to wsgi.py
index 87a0f37..0e3bac0 100644
--- a/wsgiapp.py
+++ b/wsgi.py
@@ -2,9 +2,10 @@
 """
 This is the entry point for the WSGI server
 """
+from pathlib import Path
 from stickynotes import make_app
 
-application = make_app()
+application = make_app(Path(__file__).parent)
 
 if __name__ == '__main__':
     application.run(debug=True)