Drag this up into the current technology
This commit is contained in:
parent
c32c8cbee4
commit
27e7624198
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
__pycache__
|
||||||
|
*.egg-info
|
||||||
|
*.sqlite
|
49
setup.py
Normal file
49
setup.py
Normal 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',
|
||||||
|
],
|
||||||
|
)
|
@ -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 =
|
@ -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
15
stickynotes/db.py
Normal 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
|
@ -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
|
|
||||||
|
@ -8,7 +8,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
body > .container {
|
body > .container {
|
||||||
padding: 70px 15px 0;
|
padding: 1rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container .text-muted {
|
.container .text-muted {
|
||||||
@ -27,18 +27,41 @@ 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 {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.linenos {
|
.linenos {
|
||||||
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);
|
||||||
|
}
|
||||||
|
@ -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 %}
|
51
stickynotes/templates/base.html
Normal file
51
stickynotes/templates/base.html
Normal 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 © 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>
|
@ -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 © 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>
|
|
@ -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>
|
</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>
|
<button type="submit" class="btn btn-primary">Submit</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
{% endblock %}
|
1
stickynotes/templates/raw.html
Normal file
1
stickynotes/templates/raw.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
{{source}}
|
@ -1 +0,0 @@
|
|||||||
${source}
|
|
16
stickynotes/templates/view.html
Normal file
16
stickynotes/templates/view.html
Normal 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 %}
|
@ -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>
|
|
@ -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')
|
||||||
is_recaptcha_valid = False
|
if recaptcha_secret_key:
|
||||||
try:
|
is_recaptcha_valid = False
|
||||||
is_recaptcha_valid = _is_recaptcha_valid(recaptcha_secret_key, request.form[u'g-recaptcha-response'])
|
try:
|
||||||
except KeyError:
|
is_recaptcha_valid = _is_recaptcha_valid(recaptcha_secret_key, request.form['g-recaptcha-response'])
|
||||||
flash(u'Unable to verify you, don\'t forget to complete the captcha.', u'danger')
|
except KeyError:
|
||||||
print(u'No form variable')
|
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:
|
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
|
||||||
|
@ -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)
|
Loading…
Reference in New Issue
Block a user