Drag this up into the current technology

This commit is contained in:
Raoul Snyman 2021-01-26 22:43:59 -07:00
parent c32c8cbee4
commit 27e7624198
Signed by: raoul
GPG Key ID: F55BCED79626AE9C
17 changed files with 273 additions and 214 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
__pycache__
*.egg-info
*.sqlite

49
setup.py Normal file
View File

@ -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',
],
)

View File

@ -1,5 +1,5 @@
[stickynotes] [stickynotes]
database_url = sqlite:///stickynotes.sqlite sqlalchemy_database_uri = sqlite:///stickynotes.sqlite
secret_key = yoursecretkeyhere secret_key = yoursecretkeyhere
recaptcha_site_key = recaptcha_site_key =
recaptcha_secret_key = recaptcha_secret_key =

View File

@ -2,39 +2,42 @@
""" """
StickyNotes, yet another paste bin StickyNotes, yet another paste bin
""" """
import os from configparser import ConfigParser
from ConfigParser import SafeConfigParser from pathlib import Path
from flask import Flask from flask import Flask
from flask.ext.mako import MakoTemplates
from models import init_db from stickynotes.db import db
from views import views 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 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')) if config_path:
config_parser = SafeConfigParser() config_file = config_path / 'stickynotes.cfg'
else:
config_file = Path(__file__).parent / '..' / 'stickynotes.cfg'
config_parser = ConfigParser()
config_parser.read(config_file) config_parser.read(config_file)
config = {} config = {}
for option in config_parser.options(u'stickynotes'): for option in config_parser.options('stickynotes'):
config[option.upper()] = config_parser.get(u'stickynotes', option) config[option.upper()] = config_parser.get('stickynotes', option)
print(config)
return config return config
def make_app(): def make_app(config_path=None):
""" """
Create the application object Create the application object
""" """
app = Flask(__name__) app = Flask(__name__)
# Load the config file # Load the config file
config = read_config() config = read_config(config_path)
app.config.update(config) app.config.update(config)
MakoTemplates(app) app.config.update({'SQLALCHEMY_TRACK_MODIFICATIONS': False})
init_db(config[u'DATABASE_URL']) db.init_app(app)
with app.app_context():
db.create_all()
app.register_blueprint(views) app.register_blueprint(views)
return app return app

15
stickynotes/db.py Normal file
View File

@ -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

View File

@ -4,44 +4,20 @@ The models in use
""" """
from datetime import datetime from datetime import datetime
from sqlalchemy import Column, Integer, String, Text, DateTime, create_engine, Boolean from stickynotes.db import Model, Column, Integer, String, Text, DateTime, Boolean
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, scoped_session
BaseModel = declarative_base()
db_session = None
class StickyNote(BaseModel): class StickyNote(Model):
""" """
The main (only?) table in the system The main (only?) table in the system
""" """
__tablename__ = u'sticky_notes' __tablename__ = 'sticky_notes'
id = Column(Integer, autoincrement=True, primary_key=True) id = Column(Integer, autoincrement=True, primary_key=True)
title = Column(String(255)) title = Column(String(255))
source = Column(Text) source = Column(Text)
lexer = Column(String(255), default=u'text') lexer = Column(String(255), default='text')
created = Column(DateTime, default=datetime.now, index=True) created = Column(DateTime, default=datetime.now, index=True)
expiry = Column(DateTime, default=None, index=True) expiry = Column(DateTime, default=None, index=True)
url = Column(String(255), index=True) url = Column(String(255), index=True)
private = Column(Boolean, default=False, 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

View File

@ -8,7 +8,7 @@ body {
} }
body > .container { body > .container {
padding: 70px 15px 0; padding: 1rem 0;
} }
.container .text-muted { .container .text-muted {
@ -27,8 +27,8 @@ body > .container {
padding-left: 15px; padding-left: 15px;
} }
code { code, kbd, pre, samp {
font-size: 80%; font-family: 'PT Mono', 'Hack', SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
} }
.sourcetable { .sourcetable {
@ -39,6 +39,29 @@ code {
text-align: right; text-align: right;
} }
td.linenos pre {
color: var(--light);
background-color: var(--secondary);
}
.source {
color: var(--light);
background-color: var(--dark);
}
.note-links { .note-links {
margin-bottom: 10px; margin-bottom: 10px;
} }
.form-control,
.form-control:focus {
background-color: var(--dark);
}
.form-control {
color: var(--light);
}
.form-control:focus {
color: var(--white);
}

View File

@ -1,7 +1,9 @@
<%inherit file="base.mako"/> {% extends "base.html" %}
{% block content %}
<div class="row"> <div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12"> <div class="col-xs-12 col-sm-12 col-md-12 col-lg-12">
<h2>About StickyNotes</h2> <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> <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>
</div> </div>
{% endblock %}

View File

@ -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>

View File

@ -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>

View File

@ -1,7 +1,8 @@
<%inherit file="base.mako"/> {% extends "base.html" %}
{% block content %}
<div class="row"> <div class="row">
<div class="col-xs-12 col-sm-12 col-md-12 col-lg-12"> <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"> <div class="form-group">
<textarea name="source" id="source" class="form-control" rows="20"></textarea> <textarea name="source" id="source" class="form-control" rows="20"></textarea>
</div> </div>
@ -10,19 +11,15 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="language">Language</label> <label for="language">Language</label>
<select name="language" id="language" class="form-control selectpicker" data-live-search="true" data-size="15"> <select name="language" id="language" class="form-control selectpicker" data-live-search="true" data-size="15" data-style="btn-default">
% for language in lexers: {% for language in lexers %}
% if language[0] == 'text': <option value="{{language[0]}}"{% if language[0] == 'text' %} selected{% endif %}>{{language[1]}}</option>
<option value="${language[0]}" selected>${language[1]}</option> {% endfor %}
% else:
<option value="${language[0]}">${language[1]}</option>
% endif
% endfor
</select> </select>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="expiry">Expiry</label> <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="never" selected>Never</option>
<option value="10min">10 Minutes</option> <option value="10min">10 Minutes</option>
<option value="1h">1 Hour</option> <option value="1h">1 Hour</option>
@ -32,17 +29,19 @@
<option value="1m">1 Month</option> <option value="1m">1 Month</option>
</select> </select>
</div> </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="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 </div>
{% 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> <button type="submit" class="btn btn-primary">Submit</button>
</form> </form>
</div> </div>
</div> </div>
{% endblock %}

View File

@ -0,0 +1 @@
{{source}}

View File

@ -1 +0,0 @@
${source}

View File

@ -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 %}

View File

@ -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>

View File

@ -2,29 +2,31 @@
""" """
The views The views
""" """
from datetime import timedelta, datetime
import logging import logging
from datetime import timedelta, datetime
from flask import Blueprint, redirect, request, flash, make_response, current_app import requests
from flask.ext.mako import render_template import short_url
from flask import Blueprint, redirect, request, flash, make_response, current_app, render_template
from pygments import highlight from pygments import highlight
from pygments.formatters.html import HtmlFormatter from pygments.formatters.html import HtmlFormatter
from pygments.lexers import get_lexer_by_name, get_all_lexers from pygments.lexers import get_lexer_by_name, get_all_lexers
from sqlalchemy import or_ 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__) log = logging.getLogger(__name__)
views = Blueprint(u'views', __name__) views = Blueprint('views', __name__)
EXPIRY_DELTAS = { EXPIRY_DELTAS = {
u'10min': timedelta(minutes=10), '10min': timedelta(minutes=10),
u'1d': timedelta(days=1), '1d': timedelta(days=1),
u'1w': timedelta(days=7), '1w': timedelta(days=7),
u'2w': timedelta(days=14), '2w': timedelta(days=14),
u'1m': timedelta(days=30) '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 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: if remote_ip:
data[u'remoteip'] = remote_ip data['remoteip'] = remote_ip
response = requests.post(u'https://www.google.com/recaptcha/api/siteverify', data=data) response = requests.post('https://www.google.com/recaptcha/api/siteverify', data=data)
try: try:
json_response = response.json() json_response = response.json()
print(json_response) return json_response['success']
return json_response[u'success']
except ValueError: except ValueError:
print response
return False return False
@views.route('/', methods=[u'GET']) @views.route('/', methods=['GET'])
def index(): def index():
""" """
Add a new sticky note 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()) all_lexers.sort(key=lambda x: x[1].lower())
now = datetime.utcnow() recaptcha_site_key = current_app.config.get('RECAPTCHA_SITE_KEY')
recaptcha_site_key = current_app.config.get(u'RECAPTCHA_SITE_KEY') return render_template('index.html', lexers=all_lexers, recaptcha_site_key=recaptcha_site_key)
return render_template(u'index.mako', lexers=all_lexers, recaptcha_site_key=recaptcha_site_key)
@views.route('/notes', methods=[u'GET']) @views.route('/notes', methods=['GET'])
def notes(): def notes():
""" """
Show a list of recent notes Show a list of recent notes
""" """
recent_notes = get_session().query(StickyNote)\ recent_notes = StickyNote.query\
.filter(or_(StickyNote.expiry == None, StickyNote.expiry < now))\ .filter(or_(StickyNote.expiry == None, StickyNote.expiry < datetime.utcnow()))\
.filter(~StickyNote.private)\ .filter(~StickyNote.private)\
.order_by(StickyNote.created.desc())\ .order_by(StickyNote.created.desc())\
.limit(10) .limit(10) # noqa
return render_template(u'notes.mako', recent=recent_notes) return render_template('notes.html', recent=recent_notes)
@views.route('/about', methods=[u'GET']) @views.route('/about', methods=['GET'])
def about(): def about():
""" """
Show the about page 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(): def save():
""" """
Save a sticky note Save a sticky note
""" """
# Check if the recaptcha is valid # Check if the recaptcha is valid
recaptcha_secret_key = current_app.config.get(u'RECAPTCHA_SECRET_KEY') recaptcha_secret_key = current_app.config.get('RECAPTCHA_SECRET_KEY')
if recaptcha_secret_key:
is_recaptcha_valid = False is_recaptcha_valid = False
try: try:
is_recaptcha_valid = _is_recaptcha_valid(recaptcha_secret_key, request.form[u'g-recaptcha-response']) is_recaptcha_valid = _is_recaptcha_valid(recaptcha_secret_key, request.form['g-recaptcha-response'])
except KeyError: except KeyError:
flash(u'Unable to verify you, don\'t forget to complete the captcha.', u'danger') flash('Unable to verify you, don\'t forget to complete the captcha.', 'danger')
print(u'No form variable') print('No form variable')
else:
is_recaptcha_valid = True
if not is_recaptcha_valid: if not is_recaptcha_valid:
return redirect(u'/') return redirect('/')
# Save the note # Save the note
db_session = get_session()
try: try:
created = datetime.utcnow() created = datetime.utcnow()
expiry = EXPIRY_DELTAS.get(request.form[u'expiry']) expiry = EXPIRY_DELTAS.get(request.form['expiry'])
if expiry: if expiry:
expiry = created + expiry expiry = created + expiry
source = request.form[u'source'] source = request.form['source']
lexer = request.form[u'language'] lexer = request.form['language']
title = request.form.get(u'title', u'') title = request.form.get('title', '')
private = True if request.form.get(u'private') else False private = True if request.form.get('private') else False
string_id = u''.join([source, lexer, title, created.isoformat()]) string_id = ''.join([source, lexer, title, created.isoformat()])
url = short_url.encode_url(sum([ord(char) for char in string_id]), min_length=8) url = short_url.encode_url(sum([ord(char) for char in string_id]), min_length=8)
note = StickyNote( note = StickyNote(
title=title, title=title,
@ -115,52 +116,52 @@ def save():
private=private, private=private,
url=url url=url
) )
db_session.add(note) session.add(note)
db_session.commit() session.commit()
return redirect(u'/' + note.url) return redirect('/' + note.url)
except Exception as e: except Exception as e:
flash(str(e), 'danger') flash(str(e), 'danger')
db_session.rollback() session.rollback()
return redirect(u'/') return redirect('/')
@views.route('/<string:note_url>', methods=[u'GET']) @views.route('/<string:note_url>', methods=['GET'])
def view(note_url): def view(note_url):
""" """
Show a sticky note Show a sticky note
:param note_url: The note to show :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: if not note:
flash(u'That note does not exist', 'danger') flash('That note does not exist', 'danger')
return redirect(u'/') return redirect('/')
lexer = get_lexer_by_name(note.lexer) 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) 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): def raw(note_url):
""" """
Show the raw version of a sticky note Show the raw version of a sticky note
:param note_url: The note to show :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: if not note:
flash(u'That note does not exist', 'danger') flash('That note does not exist', 'danger')
return redirect(u'/') return redirect('/')
return render_template(u'raw.mako', source=note.source), 200, {'Content-Type': 'text/plain; charset=utf-8'} 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(): def pygments_css():
""" """
Return the Pygments CSS to the browser 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' response.headers['Content-Type'] = 'text/css'
return response return response

View File

@ -2,9 +2,10 @@
""" """
This is the entry point for the WSGI server This is the entry point for the WSGI server
""" """
from pathlib import Path
from stickynotes import make_app from stickynotes import make_app
application = make_app() application = make_app(Path(__file__).parent)
if __name__ == '__main__': if __name__ == '__main__':
application.run(debug=True) application.run(debug=True)