Commit 6238dd47 authored by Romain Loth's avatar Romain Loth

cleaning up archives (essentials of the flask server are copied/translated in root dir)

parent 7859bb3e
*.sqlite *.sqlite
*.db *.db
*.json logs/nginx/*
env/ setup/regcomex_venv/*
archives_local/*
__pycache__/
web: gunicorn manage:app --log-file -
\ No newline at end of file
# app directory
This directory contains the Flask application code.
The code has been organized into the following directories:
# Code directories
models # Database Models and their Forms
startup # Application startup code and settings.py file
views # View functions
# Asset and Template directories
static # This subdirectory will be mapped to the "/static/" URL
templates # Jinja2 HTML template files
# __init__.py is a special Python file that allows a directory to become
# a Python package so it can be accessed using the 'import' statement.
from flask import Flask
from flask_script import Manager
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__) # The WSGI compliant web application object
db = SQLAlchemy(app) # Setup Flask-SQLAlchemy
manager = Manager(app) # Setup Flask-Script
from reversal import ReverseProxied
app.wsgi_app = ReverseProxied(app.wsgi_app)
from app.startup.create_app import create_app
\ No newline at end of file
# __init__.py is a special Python file that allows a directory to become
# a Python package so it can be accessed using the 'import' statement.
from . import manager
from . import models
from . import views
# This file defines command line commands for manage.py
#
# Copyright 2014 SolidBuilds.com. All rights reserved
#
# Authors: Ling Thio <ling.thio@gmail.com>
from app import manager
@manager.command
def init_db():
from app.startup.create_users import create_users
create_users()
\ No newline at end of file
# Copyright 2014 SolidBuilds.com. All rights reserved
#
# Authors: Ling Thio <ling.thio@gmail.com>
from flask_user import UserMixin
from flask_user.forms import RegisterForm
from flask_wtf import Form
from wtforms import StringField, SubmitField, validators
from app import db
# Define the User data model. Make sure to add the flask_user.UserMixin !!
class User(db.Model, UserMixin):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
# User authentication information (required for Flask-User)
email = db.Column(db.Unicode(255), nullable=False, server_default=u'', unique=True)
confirmed_at = db.Column(db.DateTime())
password = db.Column(db.String(255), nullable=False, server_default='')
reset_password_token = db.Column(db.String(100), nullable=False, server_default='')
active = db.Column(db.Boolean(), nullable=False, server_default='0')
# User information
active = db.Column('is_active', db.Boolean(), nullable=False, server_default='0')
first_name = db.Column(db.Unicode(50), nullable=False, server_default=u'')
last_name = db.Column(db.Unicode(50), nullable=False, server_default=u'')
# Relationships
roles = db.relationship('Role', secondary='users_roles',
backref=db.backref('users', lazy='dynamic'))
# Define the Role data model
class Role(db.Model):
__tablename__ = 'roles'
id = db.Column(db.Integer(), primary_key=True)
name = db.Column(db.String(50), nullable=False, server_default=u'', unique=True) # for @roles_accepted()
label = db.Column(db.Unicode(255), server_default=u'') # for display purposes
# Define the UserRoles association model
class UsersRoles(db.Model):
__tablename__ = 'users_roles'
id = db.Column(db.Integer(), primary_key=True)
user_id = db.Column(db.Integer(), db.ForeignKey('users.id', ondelete='CASCADE'))
role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE'))
# Define the User registration form
# It augments the Flask-User RegisterForm with additional fields
class MyRegisterForm(RegisterForm):
first_name = StringField('First name', validators=[
validators.DataRequired('First name is required')])
last_name = StringField('Last name', validators=[
validators.DataRequired('Last name is required')])
# Define the User profile form
class UserProfileForm(Form):
first_name = StringField('First name', validators=[
validators.DataRequired('First name is required')])
last_name = StringField('Last name', validators=[
validators.DataRequired('Last name is required')])
submit = SubmitField('Save')
# Copyright 2014 SolidBuilds.com. All rights reserved
#
# Authors: Ling Thio <ling.thio@gmail.com>
from flask import redirect, render_template, render_template_string, Blueprint
from flask import request, url_for
from flask_user import current_user, login_required, roles_accepted
from app import app, db
from app.core.models import UserProfileForm
core_blueprint = Blueprint('core', __name__, url_prefix='/')
from datetime import timedelta
from flask import make_response, request, current_app
from functools import update_wrapper
def crossdomain(origin=None, methods=None, headers=None, max_age=21600, attach_to_all=True, automatic_options=True):
if methods is not None:
methods = ', '.join(sorted(x.upper() for x in methods))
if headers is not None and not isinstance(headers, str):
headers = ', '.join(x.upper() for x in headers)
if not isinstance(origin, str):
origin = ', '.join(origin)
if isinstance(max_age, timedelta):
max_age = max_age.total_seconds()
def get_methods():
if methods is not None:
return methods
options_resp = current_app.make_default_options_response()
return options_resp.headers['allow']
def decorator(f):
def wrapped_function(*args, **kwargs):
if automatic_options and request.method == 'OPTIONS':
resp = current_app.make_default_options_response()
else:
resp = make_response(f(*args, **kwargs))
if not attach_to_all and request.method != 'OPTIONS':
return resp
h = resp.headers
h['Access-Control-Allow-Origin'] = origin
h['Access-Control-Allow-Methods'] = get_methods()
h['Access-Control-Max-Age'] = str(max_age)
if headers is not None:
h['Access-Control-Allow-Headers'] = headers
return resp
f.provide_automatic_options = False
return update_wrapper(wrapped_function, f)
return decorator
# The Home page is accessible to anyone
@core_blueprint.route('')
# @crossdomain(origin='*')
def home_page():
return render_template('core/home_page.html')
# The User page is accessible to authenticated users (users that have logged in)
@core_blueprint.route('user')
@login_required # Limits access to authenticated users
# @crossdomain(origin='*')
def user_page():
return render_template('core/user_page.html')
# The Admin page is accessible to users with the 'admin' role
@core_blueprint.route('admin')
@roles_accepted('admin') # Limits access to users with the 'admin' role
# @crossdomain(origin='*')
def admin_page():
return render_template('core/admin_page.html')
@core_blueprint.route('user/profile', methods=['GET', 'POST'])
@login_required
# @crossdomain(origin='*')
def user_profile_page():
# Initialize form
form = UserProfileForm(request.form, current_user)
# Process valid POST
if request.method == 'POST' and form.validate():
# Copy form fields to user_profile fields
form.populate_obj(current_user)
# Save user_profile
db.session.commit()
# Redirect to home page
return redirect(url_for('core.home_page'))
# Process GET or invalid POST
return render_template('core/user_profile_page.html',
form=form)
# Register blueprint
app.register_blueprint(core_blueprint)
import os
import json
# *****************************
# Environment specific settings
# *****************************
# The settings below can (and should) be over-ruled by OS environment variable settings
# Flask settings # Generated with: import os; os.urandom(24)
SECRET_KEY = '\xb9\x8d\xb5\xc2\xc4Q\xe7\x8ej\xe0\x05\xf3\xa3kp\x99l\xe7\xf2i\x00\xb1-\xcd'
# PLEASE USE A DIFFERENT KEY FOR PRODUCTION ENVIRONMENTS!
# SQLAlchemy settings
SQLALCHEMY_DATABASE_URI = 'sqlite:///../app.sqlite'
with open("credentials.json") as data_file:
C = json.load(data_file)
# Flask-Mail settings
MAIL_USERNAME = C["mail"]
MAIL_PASSWORD = C["pass"]
MAIL_DEFAULT_SENDER = '"CommunityExplorer" <noreply@communityexplorer.org>'
MAIL_SERVER = 'smtp.gmail.com'
MAIL_PORT = 465
MAIL_USE_SSL = True
MAIL_USE_TLS = False
ADMINS = [
'"Admin One" <kaislean@gmail.com>',
]
# __init__.py is a special Python file that allows a directory to become
# a Python package so it can be accessed using the 'import' statement.
# Intentionally left empty
\ No newline at end of file
import os
# ***********************************
# Settings common to all environments
# ***********************************
# Application settings
APP_NAME = "AppName"
APP_SYSTEM_ERROR_SUBJECT_LINE = APP_NAME + " system error"
# Flask settings
CSRF_ENABLED = True
# Flask-User settings
USER_APP_NAME = APP_NAME
USER_ENABLE_CHANGE_PASSWORD = True # Allow users to change their password
USER_ENABLE_CHANGE_USERNAME = False # Allow users to change their username
USER_ENABLE_CONFIRM_EMAIL = True # Force users to confirm their email
USER_ENABLE_FORGOT_PASSWORD = True # Allow users to reset their passwords
USER_ENABLE_EMAIL = True # Register with Email
USER_ENABLE_REGISTRATION = True # Allow new users to register
USER_ENABLE_RETYPE_PASSWORD = True # Prompt for `retype password` in:
USER_ENABLE_USERNAME = False # Register and Login with username
USER_AFTER_LOGIN_ENDPOINT = 'core.user_page'
USER_AFTER_LOGOUT_ENDPOINT = 'core.home_page'
# Copyright 2014 SolidBuilds.com. All rights reserved
#
# Authors: Ling Thio <ling.thio@gmail.com>
from flask_mail import Mail
from flask_migrate import Migrate, MigrateCommand
from flask_user import UserManager, SQLAlchemyAdapter
from flask_wtf.csrf import CsrfProtect
import os
from app import app, db, manager
@app.before_first_request
def initialize_app_on_first_request():
""" Create users and roles tables on first HTTP request """
from .create_users import create_users
create_users()
def create_app(extra_config_settings={}):
"""
Initialize Flask applicaton
"""
# ***** Initialize app config settings *****
# Read common settings from 'app/startup/common_settings.py' file
app.config.from_object('app.startup.common_settings')
# Read environment-specific settings from file defined by OS environment variable 'ENV_SETTINGS_FILE'
env_settings_file = os.environ.get('ENV_SETTINGS_FILE', 'env_settings_example.py')
app.config.from_pyfile(env_settings_file)
# Read extra config settings from function parameter 'extra_config_settings'
app.config.update(extra_config_settings) # Overwrite with 'extra_config_settings' parameter
if app.testing:
app.config['WTF_CSRF_ENABLED'] = False # Disable CSRF checks while testing
# Setup Flask-Migrate
migrate = Migrate(app, db)
manager.add_command('db', MigrateCommand)
# Setup Flask-Mail
mail = Mail(app)
# Setup WTForms CsrfProtect
CsrfProtect(app)
# Define bootstrap_is_hidden_field for flask-bootstrap's bootstrap_wtf.html
from wtforms.fields import HiddenField
def is_hidden_field_filter(field):
return isinstance(field, HiddenField)
app.jinja_env.globals['bootstrap_is_hidden_field'] = is_hidden_field_filter
# Setup an error-logger to send emails to app.config.ADMINS
init_email_error_handler(app)
# Setup Flask-User to handle user account related forms
from app.core.models import User, MyRegisterForm
from app.core.views import user_profile_page
db_adapter = SQLAlchemyAdapter(db, User) # Setup the SQLAlchemy DB Adapter
user_manager = UserManager(db_adapter, app, # Init Flask-User and bind to app
register_form=MyRegisterForm, # using a custom register form with UserProfile fields
user_profile_view_function=user_profile_page,
)
# Load all blueprints with their manager commands, models and views
from app import core
return app
def init_email_error_handler(app):
"""
Initialize a logger to send emails on error-level messages.
Unhandled exceptions will now send an email message to app.config.ADMINS.
"""
if app.debug: return # Do not send error emails while developing
# Retrieve email settings from app.config
host = app.config['MAIL_SERVER']
port = app.config['MAIL_PORT']
from_addr = app.config['MAIL_DEFAULT_SENDER']
username = app.config['MAIL_USERNAME']
password = app.config['MAIL_PASSWORD']
secure = () if app.config.get('MAIL_USE_TLS') else None
# Retrieve app settings from app.config
to_addr_list = app.config['ADMINS']
subject = app.config.get('APP_SYSTEM_ERROR_SUBJECT_LINE', 'System Error')
# Setup an SMTP mail handler for error-level messages
import logging
from logging.handlers import SMTPHandler
mail_handler = SMTPHandler(
mailhost=(host, port), # Mail host and port
fromaddr=from_addr, # From address
toaddrs=to_addr_list, # To address
subject=subject, # Subject line
credentials=(username, password), # Credentials
secure=secure,
)
mail_handler.setLevel(logging.ERROR)
app.logger.addHandler(mail_handler)
# Log errors using: app.logger.error('Some error message')
from datetime import datetime
from app import app, db
from app.core.models import User, Role
def create_users():
""" Create users when app starts """
from app.core.models import User, Role
# Create all tables
db.create_all()
# Adding roles
admin_role = find_or_create_role('admin', u'Admin')
# Add users
user = find_or_create_user(u'Admin', u'Example', u'admin@example.com', 'Password1', admin_role)
user = find_or_create_user(u'User', u'Example', u'user@example.com', 'Password1')
# Save to DB
db.session.commit()
def find_or_create_role(name, label):
""" Find existing role or create new role """
role = Role.query.filter(Role.name == name).first()
if not role:
role = Role(name=name, label=label)
db.session.add(role)
return role
def find_or_create_user(first_name, last_name, email, password, role=None):
""" Find existing user or create new user """
user = User.query.filter(User.email == email).first()
if not user:
user = User(email=email,
first_name=first_name,
last_name=last_name,
password=app.user_manager.hash_password(password),
active=True,
confirmed_at=datetime.utcnow())
if role:
user.roles.append(role)
db.session.add(user)
return user
/**** element styles ****/
hr { border-color: #cccccc; margin: 0px; }
/**** header, main and footer divs ****/
.header-title { font-size: 30px; }
/**** class-based style modifiers ****/
.no-margins { margin: 0px; }
.with-margins { margin: 10px; }
.col-centered { float: none; margin: 0 auto; }
/*! HTML5 Boilerplate v5.2.0 | MIT License | https://html5boilerplate.com/ */
/*
* What follows is the result of much research on cross-browser styling.
* Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal,
* Kroc Camen, and the H5BP dev community and team.
*/
/* ==========================================================================
Base styles: opinionated defaults
========================================================================== */
html {
color: #222;
font-size: 1em;
line-height: 1.4;
}
/*
* Remove text-shadow in selection highlight:
* https://twitter.com/miketaylr/status/12228805301
*
* These selection rule sets have to be separate.
* Customize the background color to match your design.
*/
::-moz-selection {
background: #b3d4fc;
text-shadow: none;
}
::selection {
background: #b3d4fc;
text-shadow: none;
}
/*
* A better looking default horizontal rule
*/
hr {
display: block;
height: 1px;
border: 0;
border-top: 1px solid #ccc;
margin: 1em 0;
padding: 0;
}
/*
* Remove the gap between audio, canvas, iframes,
* images, videos and the bottom of their containers:
* https://github.com/h5bp/html5-boilerplate/issues/440
*/
audio,
canvas,
iframe,
img,
svg,
video {
vertical-align: middle;
}
/*
* Remove default fieldset styles.
*/
fieldset {
border: 0;
margin: 0;
padding: 0;
}
/*
* Allow only vertical resizing of textareas.
*/
textarea {
resize: vertical;
}
/* ==========================================================================
Browser Upgrade Prompt
========================================================================== */
.browserupgrade {
margin: 0.2em 0;
background: #ccc;
color: #000;
padding: 0.2em 0;
}
/* ==========================================================================
Author's custom styles
========================================================================== */
/* ==========================================================================
Helper classes
========================================================================== */
/*
* Hide visually and from screen readers:
*/
.hidden {
display: none !important;
}
/*
* Hide only visually, but have it available for screen readers:
* http://snook.ca/archives/html_and_css/hiding-content-for-accessibility
*/
.visuallyhidden {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
/*
* Extends the .visuallyhidden class to allow the element
* to be focusable when navigated to via the keyboard:
* https://www.drupal.org/node/897638
*/
.visuallyhidden.focusable:active,
.visuallyhidden.focusable:focus {
clip: auto;
height: auto;
margin: 0;
overflow: visible;
position: static;
width: auto;
}
/*
* Hide visually and from screen readers, but maintain layout
*/
.invisible {
visibility: hidden;
}
/*
* Clearfix: contain floats
*
* For modern browsers
* 1. The space content is one way to avoid an Opera bug when the
* `contenteditable` attribute is included anywhere else in the document.
* Otherwise it causes space to appear at the top and bottom of elements
* that receive the `clearfix` class.
* 2. The use of `table` rather than `block` is only necessary if using
* `:before` to contain the top-margins of child elements.
*/
.clearfix:before,
.clearfix:after {
content: " "; /* 1 */
display: table; /* 2 */
}
.clearfix:after {
clear: both;
}
/* ==========================================================================
EXAMPLE Media Queries for Responsive Design.
These examples override the primary ('mobile first') styles.
Modify as content requires.
========================================================================== */
@media only screen and (min-width: 35em) {
/* Style adjustments for viewports that meet the condition */
}
@media print,
(-webkit-min-device-pixel-ratio: 1.25),
(min-resolution: 1.25dppx),
(min-resolution: 120dpi) {
/* Style adjustments for high resolution devices */
}
/* ==========================================================================
Print styles.
Inlined to avoid the additional HTTP request:
http://www.phpied.com/delay-loading-your-print-css/
========================================================================== */
@media print {
*,
*:before,
*:after {
background: transparent !important;
color: #000 !important; /* Black prints faster:
http://www.sanbeiji.com/archives/953 */
box-shadow: none !important;
text-shadow: none !important;
}
a,
a:visited {
text-decoration: underline;
}
a[href]:after {
content: " (" attr(href) ")";
}
abbr[title]:after {
content: " (" attr(title) ")";
}
/*
* Don't show links that are fragment identifiers,
* or use the `javascript:` pseudo protocol
*/
a[href^="#"]:after,
a[href^="javascript:"]:after {
content: "";
}
pre,
blockquote {
border: 1px solid #999;
page-break-inside: avoid;
}
/*
* Printing Tables:
* http://css-discuss.incutio.com/wiki/Printing_Tables
*/
thead {
display: table-header-group;
}
tr,
img {
page-break-inside: avoid;
}
img {
max-width: 100% !important;
}
p,
h2,
h3 {
orphans: 3;
widows: 3;
}
h2,
h3 {
page-break-after: avoid;
}
}
/*! normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css */
/**
* 1. Set default font family to sans-serif.
* 2. Prevent iOS and IE text size adjust after device orientation change,
* without disabling user zoom.
*/
html {
font-family: sans-serif; /* 1 */
-ms-text-size-adjust: 100%; /* 2 */
-webkit-text-size-adjust: 100%; /* 2 */
}
/**
* Remove default margin.
*/
body {
margin: 0;
}
/* HTML5 display definitions
========================================================================== */
/**
* Correct `block` display not defined for any HTML5 element in IE 8/9.
* Correct `block` display not defined for `details` or `summary` in IE 10/11
* and Firefox.
* Correct `block` display not defined for `main` in IE 11.
*/
article,
aside,
details,
figcaption,
figure,
footer,
header,
hgroup,
main,
menu,
nav,
section,
summary {
display: block;
}
/**
* 1. Correct `inline-block` display not defined in IE 8/9.
* 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
*/
audio,
canvas,
progress,
video {
display: inline-block; /* 1 */
vertical-align: baseline; /* 2 */
}
/**
* Prevent modern browsers from displaying `audio` without controls.
* Remove excess height in iOS 5 devices.
*/
audio:not([controls]) {
display: none;
height: 0;
}
/**
* Address `[hidden]` styling not present in IE 8/9/10.
* Hide the `template` element in IE 8/9/10/11, Safari, and Firefox < 22.
*/
[hidden],
template {
display: none;
}
/* Links
========================================================================== */
/**
* Remove the gray background color from active links in IE 10.
*/
a {
background-color: transparent;
}
/**
* Improve readability of focused elements when they are also in an
* active/hover state.
*/
a:active,
a:hover {
outline: 0;
}
/* Text-level semantics
========================================================================== */
/**
* Address styling not present in IE 8/9/10/11, Safari, and Chrome.
*/
abbr[title] {
border-bottom: 1px dotted;
}
/**
* Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
*/
b,
strong {
font-weight: bold;
}
/**
* Address styling not present in Safari and Chrome.
*/
dfn {
font-style: italic;
}
/**
* Address variable `h1` font-size and margin within `section` and `article`
* contexts in Firefox 4+, Safari, and Chrome.
*/
h1 {
font-size: 2em;
margin: 0.67em 0;
}
/**
* Address styling not present in IE 8/9.
*/
mark {
background: #ff0;
color: #000;
}
/**
* Address inconsistent and variable font size in all browsers.
*/
small {
font-size: 80%;
}
/**
* Prevent `sub` and `sup` affecting `line-height` in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sup {
top: -0.5em;
}
sub {
bottom: -0.25em;
}
/* Embedded content
========================================================================== */
/**
* Remove border when inside `a` element in IE 8/9/10.
*/
img {
border: 0;
}
/**
* Correct overflow not hidden in IE 9/10/11.
*/
svg:not(:root) {
overflow: hidden;
}
/* Grouping content
========================================================================== */
/**
* Address margin not present in IE 8/9 and Safari.
*/
figure {
margin: 1em 40px;
}
/**
* Address differences between Firefox and other browsers.
*/
hr {
box-sizing: content-box;
height: 0;
}
/**
* Contain overflow in all browsers.
*/
pre {
overflow: auto;
}
/**
* Address odd `em`-unit font size rendering in all browsers.
*/
code,
kbd,
pre,
samp {
font-family: monospace, monospace;
font-size: 1em;
}
/* Forms
========================================================================== */
/**
* Known limitation: by default, Chrome and Safari on OS X allow very limited
* styling of `select`, unless a `border` property is set.
*/
/**
* 1. Correct color not being inherited.
* Known issue: affects color of disabled elements.
* 2. Correct font properties not being inherited.
* 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
*/
button,
input,
optgroup,
select,
textarea {
color: inherit; /* 1 */
font: inherit; /* 2 */
margin: 0; /* 3 */
}
/**
* Address `overflow` set to `hidden` in IE 8/9/10/11.
*/
button {
overflow: visible;
}
/**
* Address inconsistent `text-transform` inheritance for `button` and `select`.
* All other form control elements do not inherit `text-transform` values.
* Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
* Correct `select` style inheritance in Firefox.
*/
button,
select {
text-transform: none;
}
/**
* 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
* and `video` controls.
* 2. Correct inability to style clickable `input` types in iOS.
* 3. Improve usability and consistency of cursor style between image-type
* `input` and others.
*/
button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
-webkit-appearance: button; /* 2 */
cursor: pointer; /* 3 */
}
/**
* Re-set default cursor for disabled elements.
*/
button[disabled],
html input[disabled] {
cursor: default;
}
/**
* Remove inner padding and border in Firefox 4+.
*/
button::-moz-focus-inner,
input::-moz-focus-inner {
border: 0;
padding: 0;
}
/**
* Address Firefox 4+ setting `line-height` on `input` using `!important` in
* the UA stylesheet.
*/
input {
line-height: normal;
}
/**
* It's recommended that you don't attempt to style these elements.
* Firefox's implementation doesn't respect box-sizing, padding, or width.
*
* 1. Address box sizing set to `content-box` in IE 8/9/10.
* 2. Remove excess padding in IE 8/9/10.
*/
input[type="checkbox"],
input[type="radio"] {
box-sizing: border-box; /* 1 */
padding: 0; /* 2 */
}
/**
* Fix the cursor style for Chrome's increment/decrement buttons. For certain
* `font-size` values of the `input`, it causes the cursor style of the
* decrement button to change from `default` to `text`.
*/
input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button {
height: auto;
}
/**
* 1. Address `appearance` set to `searchfield` in Safari and Chrome.
* 2. Address `box-sizing` set to `border-box` in Safari and Chrome.
*/
input[type="search"] {
-webkit-appearance: textfield; /* 1 */
box-sizing: content-box; /* 2 */
}
/**
* Remove inner padding and search cancel button in Safari and Chrome on OS X.
* Safari (but not Chrome) clips the cancel button when the search input has
* padding (and `textfield` appearance).
*/
input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
-webkit-appearance: none;
}
/**
* Define consistent border, margin, and padding.
*/
fieldset {
border: 1px solid #c0c0c0;
margin: 0 2px;
padding: 0.35em 0.625em 0.75em;
}
/**
* 1. Correct `color` not being inherited in IE 8/9/10/11.
* 2. Remove padding so people aren't caught out if they zero out fieldsets.
*/
legend {
border: 0; /* 1 */
padding: 0; /* 2 */
}
/**
* Remove default vertical scrollbar in IE 8/9/10/11.
*/
textarea {
overflow: auto;
}
/**
* Don't inherit the `font-weight` (applied by a rule above).
* NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
*/
optgroup {
font-weight: bold;
}
/* Tables
========================================================================== */
/**
* Remove most spacing between table cells.
*/
table {
border-collapse: collapse;
border-spacing: 0;
}
td,
th {
padding: 0;
}
/* Modernizr 2.8.3 (Custom Build) | MIT & BSD
* Build: http://modernizr.com/download/#-fontface-backgroundsize-borderimage-borderradius-boxshadow-flexbox-hsla-multiplebgs-opacity-rgba-textshadow-cssanimations-csscolumns-generatedcontent-cssgradients-cssreflections-csstransforms-csstransforms3d-csstransitions-applicationcache-canvas-canvastext-draganddrop-hashchange-history-audio-video-indexeddb-input-inputtypes-localstorage-postmessage-sessionstorage-websockets-websqldatabase-webworkers-geolocation-inlinesvg-smil-svg-svgclippaths-touch-webgl-shiv-mq-cssclasses-addtest-prefixed-teststyles-testprop-testallprops-hasevent-prefixes-domprefixes-load
*/
;window.Modernizr=function(a,b,c){function D(a){j.cssText=a}function E(a,b){return D(n.join(a+";")+(b||""))}function F(a,b){return typeof a===b}function G(a,b){return!!~(""+a).indexOf(b)}function H(a,b){for(var d in a){var e=a[d];if(!G(e,"-")&&j[e]!==c)return b=="pfx"?e:!0}return!1}function I(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:F(f,"function")?f.bind(d||b):f}return!1}function J(a,b,c){var d=a.charAt(0).toUpperCase()+a.slice(1),e=(a+" "+p.join(d+" ")+d).split(" ");return F(b,"string")||F(b,"undefined")?H(e,b):(e=(a+" "+q.join(d+" ")+d).split(" "),I(e,b,c))}function K(){e.input=function(c){for(var d=0,e=c.length;d<e;d++)u[c[d]]=c[d]in k;return u.list&&(u.list=!!b.createElement("datalist")&&!!a.HTMLDataListElement),u}("autocomplete autofocus list placeholder max min multiple pattern required step".split(" ")),e.inputtypes=function(a){for(var d=0,e,f,h,i=a.length;d<i;d++)k.setAttribute("type",f=a[d]),e=k.type!=="text",e&&(k.value=l,k.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(f)&&k.style.WebkitAppearance!==c?(g.appendChild(k),h=b.defaultView,e=h.getComputedStyle&&h.getComputedStyle(k,null).WebkitAppearance!=="textfield"&&k.offsetHeight!==0,g.removeChild(k)):/^(search|tel)$/.test(f)||(/^(url|email)$/.test(f)?e=k.checkValidity&&k.checkValidity()===!1:e=k.value!=l)),t[a[d]]=!!e;return t}("search tel url email datetime date month week time datetime-local number range color".split(" "))}var d="2.8.3",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k=b.createElement("input"),l=":)",m={}.toString,n=" -webkit- -moz- -o- -ms- ".split(" "),o="Webkit Moz O ms",p=o.split(" "),q=o.toLowerCase().split(" "),r={svg:"http://www.w3.org/2000/svg"},s={},t={},u={},v=[],w=v.slice,x,y=function(a,c,d,e){var f,i,j,k,l=b.createElement("div"),m=b.body,n=m||b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),l.appendChild(j);return f=["&#173;",'<style id="s',h,'">',a,"</style>"].join(""),l.id=h,(m?l:n).innerHTML+=f,n.appendChild(l),m||(n.style.background="",n.style.overflow="hidden",k=g.style.overflow,g.style.overflow="hidden",g.appendChild(n)),i=c(l,a),m?l.parentNode.removeChild(l):(n.parentNode.removeChild(n),g.style.overflow=k),!!i},z=function(b){var c=a.matchMedia||a.msMatchMedia;if(c)return c(b)&&c(b).matches||!1;var d;return y("@media "+b+" { #"+h+" { position: absolute; } }",function(b){d=(a.getComputedStyle?getComputedStyle(b,null):b.currentStyle)["position"]=="absolute"}),d},A=function(){function d(d,e){e=e||b.createElement(a[d]||"div"),d="on"+d;var f=d in e;return f||(e.setAttribute||(e=b.createElement("div")),e.setAttribute&&e.removeAttribute&&(e.setAttribute(d,""),f=F(e[d],"function"),F(e[d],"undefined")||(e[d]=c),e.removeAttribute(d))),e=null,f}var a={select:"input",change:"input",submit:"form",reset:"form",error:"img",load:"img",abort:"img"};return d}(),B={}.hasOwnProperty,C;!F(B,"undefined")&&!F(B.call,"undefined")?C=function(a,b){return B.call(a,b)}:C=function(a,b){return b in a&&F(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=w.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(w.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(w.call(arguments)))};return e}),s.flexbox=function(){return J("flexWrap")},s.canvas=function(){var a=b.createElement("canvas");return!!a.getContext&&!!a.getContext("2d")},s.canvastext=function(){return!!e.canvas&&!!F(b.createElement("canvas").getContext("2d").fillText,"function")},s.webgl=function(){return!!a.WebGLRenderingContext},s.touch=function(){var c;return"ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch?c=!0:y(["@media (",n.join("touch-enabled),("),h,")","{#modernizr{top:9px;position:absolute}}"].join(""),function(a){c=a.offsetTop===9}),c},s.geolocation=function(){return"geolocation"in navigator},s.postmessage=function(){return!!a.postMessage},s.websqldatabase=function(){return!!a.openDatabase},s.indexedDB=function(){return!!J("indexedDB",a)},s.hashchange=function(){return A("hashchange",a)&&(b.documentMode===c||b.documentMode>7)},s.history=function(){return!!a.history&&!!history.pushState},s.draganddrop=function(){var a=b.createElement("div");return"draggable"in a||"ondragstart"in a&&"ondrop"in a},s.websockets=function(){return"WebSocket"in a||"MozWebSocket"in a},s.rgba=function(){return D("background-color:rgba(150,255,150,.5)"),G(j.backgroundColor,"rgba")},s.hsla=function(){return D("background-color:hsla(120,40%,100%,.5)"),G(j.backgroundColor,"rgba")||G(j.backgroundColor,"hsla")},s.multiplebgs=function(){return D("background:url(https://),url(https://),red url(https://)"),/(url\s*\(.*?){3}/.test(j.background)},s.backgroundsize=function(){return J("backgroundSize")},s.borderimage=function(){return J("borderImage")},s.borderradius=function(){return J("borderRadius")},s.boxshadow=function(){return J("boxShadow")},s.textshadow=function(){return b.createElement("div").style.textShadow===""},s.opacity=function(){return E("opacity:.55"),/^0.55$/.test(j.opacity)},s.cssanimations=function(){return J("animationName")},s.csscolumns=function(){return J("columnCount")},s.cssgradients=function(){var a="background-image:",b="gradient(linear,left top,right bottom,from(#9f9),to(white));",c="linear-gradient(left top,#9f9, white);";return D((a+"-webkit- ".split(" ").join(b+a)+n.join(c+a)).slice(0,-a.length)),G(j.backgroundImage,"gradient")},s.cssreflections=function(){return J("boxReflect")},s.csstransforms=function(){return!!J("transform")},s.csstransforms3d=function(){var a=!!J("perspective");return a&&"webkitPerspective"in g.style&&y("@media (transform-3d),(-webkit-transform-3d){#modernizr{left:9px;position:absolute;height:3px;}}",function(b,c){a=b.offsetLeft===9&&b.offsetHeight===3}),a},s.csstransitions=function(){return J("transition")},s.fontface=function(){var a;return y('@font-face {font-family:"font";src:url("https://")}',function(c,d){var e=b.getElementById("smodernizr"),f=e.sheet||e.styleSheet,g=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"";a=/src/i.test(g)&&g.indexOf(d.split(" ")[0])===0}),a},s.generatedcontent=function(){var a;return y(["#",h,"{font:0/0 a}#",h,':after{content:"',l,'";visibility:hidden;font:3px/1 a}'].join(""),function(b){a=b.offsetHeight>=3}),a},s.video=function(){var a=b.createElement("video"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('video/ogg; codecs="theora"').replace(/^no$/,""),c.h264=a.canPlayType('video/mp4; codecs="avc1.42E01E"').replace(/^no$/,""),c.webm=a.canPlayType('video/webm; codecs="vp8, vorbis"').replace(/^no$/,"")}catch(d){}return c},s.audio=function(){var a=b.createElement("audio"),c=!1;try{if(c=!!a.canPlayType)c=new Boolean(c),c.ogg=a.canPlayType('audio/ogg; codecs="vorbis"').replace(/^no$/,""),c.mp3=a.canPlayType("audio/mpeg;").replace(/^no$/,""),c.wav=a.canPlayType('audio/wav; codecs="1"').replace(/^no$/,""),c.m4a=(a.canPlayType("audio/x-m4a;")||a.canPlayType("audio/aac;")).replace(/^no$/,"")}catch(d){}return c},s.localstorage=function(){try{return localStorage.setItem(h,h),localStorage.removeItem(h),!0}catch(a){return!1}},s.sessionstorage=function(){try{return sessionStorage.setItem(h,h),sessionStorage.removeItem(h),!0}catch(a){return!1}},s.webworkers=function(){return!!a.Worker},s.applicationcache=function(){return!!a.applicationCache},s.svg=function(){return!!b.createElementNS&&!!b.createElementNS(r.svg,"svg").createSVGRect},s.inlinesvg=function(){var a=b.createElement("div");return a.innerHTML="<svg/>",(a.firstChild&&a.firstChild.namespaceURI)==r.svg},s.smil=function(){return!!b.createElementNS&&/SVGAnimate/.test(m.call(b.createElementNS(r.svg,"animate")))},s.svgclippaths=function(){return!!b.createElementNS&&/SVGClipPath/.test(m.call(b.createElementNS(r.svg,"clipPath")))};for(var L in s)C(s,L)&&(x=L.toLowerCase(),e[x]=s[L](),v.push((e[x]?"":"no-")+x));return e.input||K(),e.addTest=function(a,b){if(typeof a=="object")for(var d in a)C(a,d)&&e.addTest(d,a[d]);else{a=a.toLowerCase();if(e[a]!==c)return e;b=typeof b=="function"?b():b,typeof f!="undefined"&&f&&(g.className+=" "+(b?"":"no-")+a),e[a]=b}return e},D(""),i=k=null,function(a,b){function l(a,b){var c=a.createElement("p"),d=a.getElementsByTagName("head")[0]||a.documentElement;return c.innerHTML="x<style>"+b+"</style>",d.insertBefore(c.lastChild,d.firstChild)}function m(){var a=s.elements;return typeof a=="string"?a.split(" "):a}function n(a){var b=j[a[h]];return b||(b={},i++,a[h]=i,j[i]=b),b}function o(a,c,d){c||(c=b);if(k)return c.createElement(a);d||(d=n(c));var g;return d.cache[a]?g=d.cache[a].cloneNode():f.test(a)?g=(d.cache[a]=d.createElem(a)).cloneNode():g=d.createElem(a),g.canHaveChildren&&!e.test(a)&&!g.tagUrn?d.frag.appendChild(g):g}function p(a,c){a||(a=b);if(k)return a.createDocumentFragment();c=c||n(a);var d=c.frag.cloneNode(),e=0,f=m(),g=f.length;for(;e<g;e++)d.createElement(f[e]);return d}function q(a,b){b.cache||(b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag()),a.createElement=function(c){return s.shivMethods?o(c,a,b):b.createElem(c)},a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/[\w\-]+/g,function(a){return b.createElem(a),b.frag.createElement(a),'c("'+a+'")'})+");return n}")(s,b.frag)}function r(a){a||(a=b);var c=n(a);return s.shivCSS&&!g&&!c.hasCSS&&(c.hasCSS=!!l(a,"article,aside,dialog,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}mark{background:#FF0;color:#000}template{display:none}")),k||q(a,c),a}var c="3.7.0",d=a.html5||{},e=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,f=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,g,h="_html5shiv",i=0,j={},k;(function(){try{var a=b.createElement("a");a.innerHTML="<xyz></xyz>",g="hidden"in a,k=a.childNodes.length==1||function(){b.createElement("a");var a=b.createDocumentFragment();return typeof a.cloneNode=="undefined"||typeof a.createDocumentFragment=="undefined"||typeof a.createElement=="undefined"}()}catch(c){g=!0,k=!0}})();var s={elements:d.elements||"abbr article aside audio bdi canvas data datalist details dialog figcaption figure footer header hgroup main mark meter nav output progress section summary template time video",version:c,shivCSS:d.shivCSS!==!1,supportsUnknownElements:k,shivMethods:d.shivMethods!==!1,type:"default",shivDocument:r,createElement:o,createDocumentFragment:p};a.html5=s,r(b)}(this,b),e._version=d,e._prefixes=n,e._domPrefixes=q,e._cssomPrefixes=p,e.mq=z,e.hasEvent=A,e.testProp=function(a){return H([a])},e.testAllProps=J,e.testStyles=y,e.prefixed=function(a,b,c){return b?J(a,b,c):J(a,"pfx")},g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+v.join(" "):""),e}(this,this.document),function(a,b,c){function d(a){return"[object Function]"==o.call(a)}function e(a){return"string"==typeof a}function f(){}function g(a){return!a||"loaded"==a||"complete"==a||"uninitialized"==a}function h(){var a=p.shift();q=1,a?a.t?m(function(){("c"==a.t?B.injectCss:B.injectJs)(a.s,0,a.a,a.x,a.e,1)},0):(a(),h()):q=0}function i(a,c,d,e,f,i,j){function k(b){if(!o&&g(l.readyState)&&(u.r=o=1,!q&&h(),l.onload=l.onreadystatechange=null,b)){"img"!=a&&m(function(){t.removeChild(l)},50);for(var d in y[c])y[c].hasOwnProperty(d)&&y[c][d].onload()}}var j=j||B.errorTimeout,l=b.createElement(a),o=0,r=0,u={t:d,s:c,e:f,a:i,x:j};1===y[c]&&(r=1,y[c]=[]),"object"==a?l.data=c:(l.src=c,l.type=a),l.width=l.height="0",l.onerror=l.onload=l.onreadystatechange=function(){k.call(this,r)},p.splice(e,0,u),"img"!=a&&(r||2===y[c]?(t.insertBefore(l,s?null:n),m(k,j)):y[c].push(l))}function j(a,b,c,d,f){return q=0,b=b||"j",e(a)?i("c"==b?v:u,a,b,this.i++,c,d,f):(p.splice(this.i++,0,a),1==p.length&&h()),this}function k(){var a=B;return a.loader={load:j,i:0},a}var l=b.documentElement,m=a.setTimeout,n=b.getElementsByTagName("script")[0],o={}.toString,p=[],q=0,r="MozAppearance"in l.style,s=r&&!!b.createRange().compareNode,t=s?l:n.parentNode,l=a.opera&&"[object Opera]"==o.call(a.opera),l=!!b.attachEvent&&!l,u=r?"object":l?"script":"img",v=l?"script":u,w=Array.isArray||function(a){return"[object Array]"==o.call(a)},x=[],y={},z={timeout:function(a,b){return b.length&&(a.timeout=b[0]),a}},A,B;B=function(a){function b(a){var a=a.split("!"),b=x.length,c=a.pop(),d=a.length,c={url:c,origUrl:c,prefixes:a},e,f,g;for(f=0;f<d;f++)g=a[f].split("="),(e=z[g.shift()])&&(c=e(c,g));for(f=0;f<b;f++)c=x[f](c);return c}function g(a,e,f,g,h){var i=b(a),j=i.autoCallback;i.url.split(".").pop().split("?").shift(),i.bypass||(e&&(e=d(e)?e:e[a]||e[g]||e[a.split("/").pop().split("?")[0]]),i.instead?i.instead(a,e,f,g,h):(y[i.url]?i.noexec=!0:y[i.url]=1,f.load(i.url,i.forceCSS||!i.forceJS&&"css"==i.url.split(".").pop().split("?").shift()?"c":c,i.noexec,i.attrs,i.timeout),(d(e)||d(j))&&f.load(function(){k(),e&&e(i.origUrl,h,g),j&&j(i.origUrl,h,g),y[i.url]=2})))}function h(a,b){function c(a,c){if(a){if(e(a))c||(j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}),g(a,j,b,0,h);else if(Object(a)===a)for(n in m=function(){var b=0,c;for(c in a)a.hasOwnProperty(c)&&b++;return b}(),a)a.hasOwnProperty(n)&&(!c&&!--m&&(d(j)?j=function(){var a=[].slice.call(arguments);k.apply(this,a),l()}:j[n]=function(a){return function(){var b=[].slice.call(arguments);a&&a.apply(this,b),l()}}(k[n])),g(a[n],j,b,n,h))}else!c&&l()}var h=!!a.test,i=a.load||a.both,j=a.callback||f,k=j,l=a.complete||f,m,n;c(h?a.yep:a.nope,!!i),i&&c(i)}var i,j,l=this.yepnope.loader;if(e(a))g(a,0,l,0);else if(w(a))for(i=0;i<a.length;i++)j=a[i],e(j)?g(j,0,l,0):w(j)?B(j):Object(j)===j&&h(j,l);else Object(a)===a&&h(a,l)},B.addPrefix=function(a,b){z[a]=b},B.addFilter=function(a){x.push(a)},B.errorTimeout=1e4,null==b.readyState&&b.addEventListener&&(b.readyState="loading",b.addEventListener("DOMContentLoaded",A=function(){b.removeEventListener("DOMContentLoaded",A,0),b.readyState="complete"},0)),a.yepnope=k(),a.yepnope.executeStack=h,a.yepnope.injectJs=function(a,c,d,e,i,j){var k=b.createElement("script"),l,o,e=e||B.errorTimeout;k.src=a;for(o in d)k.setAttribute(o,d[o]);c=j?h:c||f,k.onreadystatechange=k.onload=function(){!l&&g(k.readyState)&&(l=1,c(),k.onload=k.onreadystatechange=null)},m(function(){l||(l=1,c(1))},e),i?k.onload():n.parentNode.insertBefore(k,n)},a.yepnope.injectCss=function(a,c,d,e,g,i){var e=b.createElement("link"),j,c=i?h:c||f;e.href=a,e.rel="stylesheet",e.type="text/css";for(j in d)e.setAttribute(j,d[j]);g||(n.parentNode.insertBefore(e,n),m(c,0))}}(this,document),Modernizr.load=function(){yepnope.apply(window,[].slice.call(arguments,0))};
// Avoid `console` errors in browsers that lack a console.
(function() {
var method;
var noop = function () {};
var methods = [
'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error',
'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log',
'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd',
'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn'
];
var length = methods.length;
var console = (window.console = window.console || {});
while (length--) {
method = methods[length];
// Only stub undefined methods.
if (!console[method]) {
console[method] = noop;
}
}
}());
// Place any jQuery/helper plugins in here.
# This directory contains Jinja2 template files
This Flask application uses the Jinja2 templating engine to render
data into HTML files.
The template files are organized into the following directories:
common # Common base templates and macros
flask_user # Flask-User template files (register, login, etc.)
pages # Templates for Page objects
users # Templates for User objects
Flask-User makes use of standard template files that reside in
`PATH/TO/VIRTUALENV/lib/PYTHONVERSION/site-packages/flask_user/templates/flask_user/`.
These standard templates can be overruled by placing a copy in the `app/templates/flask_user/` directory.
{% macro render_field(field, label=None, label_visible=true, right_url=None, right_label=None) -%}
<div class="form-group {% if field.errors %}has-error{% endif %} {{ kwargs.pop('class_', '') }}">
{% if field.type != 'HiddenField' and label_visible %}
{% if not label %}{% set label=field.label.text %}{% endif %}
<label for="{{ field.id }}" class="control-label">{{ label|safe }}</label>
{% endif %}
{{ field(class_='form-control', **kwargs) }}
{% if field.errors %}
{% for e in field.errors %}
<p class="help-block">{{ e }}</p>
{% endfor %}
{% endif %}
</div>
{%- endmacro %}
{% macro render_checkbox_field(field, label=None) -%}
{% if not label %}{% set label=field.label.text %}{% endif %}
<div class="checkbox">
<label>
{{ field(type='checkbox', **kwargs) }} {{ label }}
</label>
</div>
{%- endmacro %}
{% macro render_radio_field(field) -%}
{% for value, label, checked in field.iter_choices() %}
<div class="radio">
<label>
<input type="radio" name="{{ field.id }}" id="{{ field.id }}" value="{{ value }}"{% if checked %} checked{% endif %}>
{{ label }}
</label>
</div>
{% endfor %}
{%- endmacro %}
{% macro render_submit_field(field, label=None, tabindex=None) -%}
{% if not label %}{% set label=field.label.text %}{% endif %}
{#<button type="submit" class="form-control btn btn-default btn-primary">{{label}}</button>#}
<input type="submit" value="{{label}}"
{% if tabindex %}tabindex="{{ tabindex }}"{% endif %}
>
{%- endmacro %}
{% extends "common/page_base.html" %} {# common/page_base.html extends layout.html #}
{% block content %}
<h1>Admin page</h1>
<p>This page is accessible to authenticated users with the 'admin' role.</p>
{% endblock %}
\ No newline at end of file
{% extends "common/page_base.html" %} {# common/page_base.html extends layout.html #}
{% block content %}
<h1>Home page</h1>
<p>This page is accessible to any user (signed in or not)</p>
{% if not current_user.is_authenticated %}
<p>
To view the User page, you must
<a href="{{ url_for('user.login') }}">Sign in</a> or
<a href="{{ url_for('user.register') }}">Register</a>.
</p>
{% endif %}
<p>
Test users:<br/>
- user@example.com / Password1<br/>
- admin@example.com / Password1
</p>
{% endblock %}
{% extends "common/page_base.html" %} {# common/page_base.html extends layout.html #}
{% block content %}
<h1>User page</h1>
<p>This page is accessible to authenticated user (users that have signed in).</p>
{% endblock %}
\ No newline at end of file
{% extends "common/page_base.html" %} {# common/page_base.html extends layout.html #}
{% block content %}
<h1>User Profile</h1>
<p><a href="{{ url_for('user.change_password') }}">Change password</a></p>
{% from "common/form_macros.html" import render_field, render_submit_field %}
<form action="" method="POST" class="form" role="form">
<div class="row">
<div class="col-sm-6 col-md-5 col-lg-4">
{{ form.hidden_tag() }}
{{ render_field(form.first_name, tabindex=240) }}
{{ render_field(form.last_name, tabindex=250) }}
{{ render_submit_field(form.submit, tabindex=280) }}
</div>
</div>
</form>
{% endblock %}
\ No newline at end of file
{% extends "layout.html" %}
{% block pre_content %}
<div class="container">
<div class="row">
<div class="col-sm-7 col-md-6 col-lg-5 col-centered">
{% endblock %}
{% block post_content %}
</div>
</div>
</div>
{% endblock %}
\ No newline at end of file
{% extends 'flask_user/public_base.html' %}
{% block content %}
{% from "flask_user/_macros.html" import render_field, render_submit_field %}
<h1>{%trans%}Register{%endtrans%}</h1>
<form action="" method="POST" novalidate formnovalidate class="form" role="form">
{{ form.hidden_tag() }}
{# Username or Email #}
{% set field = form.username if user_manager.enable_username else form.email %}
<div class="form-group {% if field.errors %}has-error{% endif %}">
{# Label on left, "Already registered? Sign in." on right #}
<div class="row">
<div class="col-xs-6">
<label for="{{ field.id }}" class="control-label">{{ field.label.text }}</label>
</div>
<div class="col-xs-6 text-right">
{% if user_manager.enable_register %}
<a href="{{ url_for('user.login') }}" tabindex='290'>
{%trans%}Already registered? Sign in.{%endtrans%}</a>
{% endif %}
</div>
</div>
{{ field(class_='form-control', tabindex=210) }}
{% if field.errors %}
{% for e in field.errors %}
<p class="help-block">{{ e }}</p>
{% endfor %}
{% endif %}
</div>
{% if user_manager.enable_email and user_manager.enable_username %}
{{ render_field(form.email, tabindex=220) }}
{% endif %}
{{ render_field(form.first_name, tabindex=240) }}
{{ render_field(form.last_name, tabindex=250) }}
{{ render_field(form.password, tabindex=260) }}
{% if user_manager.enable_retype_password %}
{{ render_field(form.retype_password, tabindex=270) }}
{% endif %}
{{ render_submit_field(form.submit, tabindex=280) }}
</form>
{% endblock %}
\ No newline at end of file
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>MyApp</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- Optional apple-touch-icon
<link rel="apple-touch-icon" href="apple-touch-icon.png"> -->
<!-- Place favicon.ico in the root directory -->
<!-- HTML5 boilerplate CSS -->
<link rel="stylesheet" href="/static/html5boilerplate/css/normalize.css">
<link rel="stylesheet" href="/static/html5boilerplate/css/main.css">
<script src="/static/html5boilerplate/js/modernizr-2.8.3.min.js"></script>
<!-- Application specific CSS-->
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/app/app.css">
</head>
<body>
{% block body %}
<!-- Application specific HTML -->
<div id="header-div" class="clearfix with-margins">
<div class="pull-left"><a href="{{ url_for('core.home_page') }}"><span class="header-title">MyApp</span></a></div>
<div class="pull-right">
{% if current_user.is_authenticated %}
<a href="{{ url_for('core.user_profile_page') }}">{{ current_user.first_name or current_user.user_auth.username }}</a>
&nbsp; | &nbsp;
<a href="{{ url_for('user.logout') }}">Sign out</a>
{% else %}
<a href="{{ url_for('user.login') }}">Sign in</a>
{% endif %}
</div>
</div>
<div class="with-margins">
<a href="{{ url_for('core.home_page') }}">Home</a>
&nbsp; | &nbsp; <a href="{{ url_for('core.user_page') }}">User</a>
&nbsp; | &nbsp; <a href="{{ url_for('core.admin_page') }}">Admin</a>
</div>
<hr class="no-margins"/>
<div id="main-div" class="with-margins">
{% block pre_content %}{% endblock %}
{# One-time system messages called Flash messages #}
{% block flash_messages %}
{%- with messages = get_flashed_messages(with_categories=true) -%}
{% if messages %}
{% for category, message in messages %}
{% if category=='error' %}
{% set category='danger' %}
{% endif %}
<div class="alert alert-{{category}}">{{ message|safe }}</div>
{% endfor %}
{% endif %}
{%- endwith %}
{% endblock %}
{% block content %}{% endblock %}
{% block post_content %}{% endblock %}
</div>
<br/>
<hr class="no-margins"/>
<div id="footer-div" class="clearfix with-margins">
<div class="pull-left">MyApp v1.0</div>
<div class="pull-right">&copy; 2014 MyCorp</div>
</div>
<!-- Application specific JS -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script src="http://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<!-- HTML5 boilerplate JS -->
<script src="/static/html5boilerplate/js/main.js"></script>
<!-- Optional Google Analytics: change UA-XXXXX-X to be your site's ID.
<script>
(function(b,o,i,l,e,r){b.GoogleAnalyticsObject=l;b[l]||(b[l]=
function(){(b[l].q=b[l].q||[]).push(arguments)});b[l].l=+new Date;
e=o.createElement(i);r=o.getElementsByTagName(i)[0];
e.src='https://www.google-analytics.com/analytics.js';
r.parentNode.insertBefore(e,r)}(window,document,'script','ga'));
ga('create','UA-XXXXX-X','auto');ga('send','pageview');
</script>
-->
{% endblock %}
</body>
</html>
from datetime import timedelta
from flask import make_response, request, current_app
from functools import update_wrapper
def crossdomain(origin=None, methods=None, headers=None,
max_age=21600, attach_to_all=True,
automatic_options=True):
if methods is not None:
methods = ', '.join(sorted(x.upper() for x in methods))
if headers is not None and not isinstance(headers, str):
headers = ', '.join(x.upper() for x in headers)
if not isinstance(origin, str):
origin = ', '.join(origin)
if isinstance(max_age, timedelta):
max_age = max_age.total_seconds()
def get_methods():
if methods is not None:
return methods
options_resp = current_app.make_default_options_response()
return options_resp.headers['allow']
def decorator(f):
def wrapped_function(*args, **kwargs):
if automatic_options and request.method == 'OPTIONS':
resp = current_app.make_default_options_response()
else:
resp = make_response(f(*args, **kwargs))
if not attach_to_all and request.method != 'OPTIONS':
return resp
h = resp.headers
h['Access-Control-Allow-Origin'] = origin
h['Access-Control-Allow-Methods'] = get_methods()
h['Access-Control-Max-Age'] = str(max_age)
if headers is not None:
h['Access-Control-Allow-Headers'] = headers
return resp
f.provide_automatic_options = False
return update_wrapper(wrapped_function, f)
return decorator
\ No newline at end of file
# This file starts the WSGI web application.
# - Heroku starts gunicorn, which loads Procfile, which starts manage.py
# - Developers can run it from the command line: python runserver.py
from app import create_app
app = create_app()
# Start a development web server if executed from the command line
if __name__ == "__main__":
# Manage the command line parameters such as:
# - python manage.py runserver
# - python manage.py db
from app import manager
manager.run()
# migrations directory
This directory has been generated by Flask-Migrate and contains Alembic configuration files.
Alembic is a Database Schema Migration tool.
The 'versions' subdirectory contains the Database Schema Migration files.
Use `python app.py db` to see a list of Flask-Migrate commands.
See also [Flask-Migrate](flask-migrate.readthedocs.org) and [Alembic](alembic.readthedocs.org).
\ No newline at end of file
# A generic, single database configuration.
[alembic]
# template used to generate migration files
# file_template = %%(rev)s_%%(slug)s
# set to 'true' to run the environment during
# the 'revision' command, regardless of autogenerate
# revision_environment = false
# Logging configuration
[loggers]
keys = root,sqlalchemy,alembic
[handlers]
keys = console
[formatters]
keys = generic
[logger_root]
level = WARN
handlers = console
qualname =
[logger_sqlalchemy]
level = WARN
handlers =
qualname = sqlalchemy.engine
[logger_alembic]
level = INFO
handlers =
qualname = alembic
[handler_console]
class = StreamHandler
args = (sys.stderr,)
level = NOTSET
formatter = generic
[formatter_generic]
format = %(levelname)-5.5s [%(name)s] %(message)s
datefmt = %H:%M:%S
from __future__ import with_statement
from alembic import context
from sqlalchemy import engine_from_config, pool
from logging.config import fileConfig
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
# Interpret the config file for Python logging.
# This line sets up loggers basically.
fileConfig(config.config_file_name)
# add your model's MetaData object here
# for 'autogenerate' support
# from myapp import mymodel
# target_metadata = mymodel.Base.metadata
from flask import current_app
config.set_main_option('sqlalchemy.url', current_app.config.get('SQLALCHEMY_DATABASE_URI'))
target_metadata = current_app.extensions['migrate'].db.metadata
# other values from the config, defined by the needs of env.py,
# can be acquired:
# my_important_option = config.get_main_option("my_important_option")
# ... etc.
def run_migrations_offline():
"""Run migrations in 'offline' mode.
This configures the context with just a URL
and not an Engine, though an Engine is acceptable
here as well. By skipping the Engine creation
we don't even need a DBAPI to be available.
Calls to context.execute() here emit the given string to the
script output.
"""
url = config.get_main_option("sqlalchemy.url")
context.configure(url=url)
with context.begin_transaction():
context.run_migrations()
def run_migrations_online():
"""Run migrations in 'online' mode.
In this scenario we need to create an Engine
and associate a connection with the context.
"""
engine = engine_from_config(config.get_section(config.config_ini_section),
prefix='sqlalchemy.',
poolclass=pool.NullPool)
connection = engine.connect()
context.configure(connection=connection,
target_metadata=target_metadata,
**current_app.extensions['migrate'].configure_args)
try:
with context.begin_transaction():
context.run_migrations()
finally:
connection.close()
if context.is_offline_mode():
run_migrations_offline()
else:
run_migrations_online()
"""${message}
Revision ID: ${up_revision}
Revises: ${down_revision}
Create Date: ${create_date}
"""
# revision identifiers, used by Alembic.
revision = ${repr(up_revision)}
down_revision = ${repr(down_revision)}
from alembic import op
import sqlalchemy as sa
${imports if imports else ""}
def upgrade():
${upgrades if upgrades else "pass"}
def downgrade():
${downgrades if downgrades else "pass"}
"""Initial version
Revision ID: 0001c8ac1a69
Revises: None
Create Date: 2015-07-14 17:46:32.620018
"""
# revision identifiers, used by Alembic.
revision = '0001c8ac1a69'
down_revision = None
from alembic import op
import sqlalchemy as sa
def upgrade():
### commands auto generated by Alembic - please adjust! ###
op.create_table('role',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('name', sa.String(length=50), nullable=True),
sa.Column('description', sa.String(length=255), server_default='', nullable=True),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('name')
)
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=50), nullable=False),
sa.Column('password', sa.String(length=255), server_default='', nullable=False),
sa.Column('reset_password_token', sa.String(length=100), server_default='', nullable=False),
sa.Column('email', sa.String(length=255), nullable=False),
sa.Column('confirmed_at', sa.DateTime(), nullable=True),
sa.Column('is_active', sa.Boolean(), server_default='0', nullable=False),
sa.Column('first_name', sa.String(length=50), server_default='', nullable=False),
sa.Column('last_name', sa.String(length=50), server_default='', nullable=False),
sa.PrimaryKeyConstraint('id'),
sa.UniqueConstraint('email'),
sa.UniqueConstraint('username')
)
op.create_table('user_roles',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('user_id', sa.Integer(), nullable=True),
sa.Column('role_id', sa.Integer(), nullable=True),
sa.ForeignKeyConstraint(['role_id'], ['role.id'], ondelete='CASCADE'),
sa.ForeignKeyConstraint(['user_id'], ['user.id'], ondelete='CASCADE'),
sa.PrimaryKeyConstraint('id')
)
### end Alembic commands ###
def downgrade():
### commands auto generated by Alembic - please adjust! ###
op.drop_table('user_roles')
op.drop_table('user')
op.drop_table('role')
### end Alembic commands ###
# This file is used by pip to install required python packages
# Usage: pip install -r requirements.txt
# Heroku environment
gunicorn==19.3.0
psycopg2==2.5.4
# Flask Framework
Flask==0.10.1
# Flask Packages
Flask-Migrate==1.4.0
Flask-SQLAlchemy==2.0
Flask-User==0.6.3
Flask-WTF==0.12
# Development tools
pytest==2.7.2
pytest-flask==0.8.1
pytest-cov==1.8.1
#
# from reversal import ReverseProxied
# more: http://flask.pocoo.org/snippets/35/
class ReverseProxied(object):
def __init__(self, app):
self.app = app
def __call__(self, environ, start_response):
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
if script_name:
environ['SCRIPT_NAME'] = script_name
path_info = environ['PATH_INFO']
if path_info.startswith(script_name):
environ['PATH_INFO'] = path_info[len(script_name):]
scheme = environ.get('HTTP_X_SCHEME', '')
if scheme:
environ['wsgi.url_scheme'] = scheme
return self.app(environ, start_response)
\ No newline at end of file
#!/bin/bash
py.test -s --cov-report term-missing --cov-config tests/.coveragerc --cov app tests/
\ No newline at end of file
#!/bin/bash
# Run a Flask development server
# --debug # Run it in DEBUG mode.
# --reload # Restart when a python file changed
python manage.py runserver --debug --reload
#!/bin/bash
py.test -s tests/
\ No newline at end of file
# tests directory
This directory contains all the automated tests for the test tool `py.test`.
**`.coverage`**: Configuration file for the Python coverage tool `coverage`.
**`conftest.py`**: Defines fixtures for py.test.
**`test_*`**: py.test will load any file that starts with the name `test_`
and run any function that starts with the name `test_`.
## Testing the app
# Run all the automated tests in the tests/ directory
./runtests.sh # will run "py.test -s tests/"
## Generating a test coverage report
# Run tests and show a test coverage report
./runcoverage.sh # will run py.test with coverage options
# __init__.py is a special Python file that allows a directory to become
# a Python package so it can be accessed using the 'import' statement.
# Intentionally left empty
\ No newline at end of file
# This file contains pytest 'fixtures'.
# If a test functions specifies the name of a fixture function as a parameter,
# the fixture function is called and its result is passed to the test function.
#
# Copyright 2014 SolidBuilds.com. All rights reserved
#
# Authors: Ling Thio <ling.thio@gmail.com>
import pytest
from app import create_app, db as the_db
# Initialize the Flask-App with test-specific settings
the_app = create_app(dict(
TESTING=True, # Propagate exceptions
LOGIN_DISABLED=False, # Enable @register_required
MAIL_SUPPRESS_SEND=True, # Disable Flask-Mail send
SERVER_NAME='localhost', # Enable url_for() without request context
SQLALCHEMY_DATABASE_URI='sqlite:///:memory:', # In-memory SQLite DB
WTF_CSRF_ENABLED=False, # Disable CSRF form validation
))
# Create and populate roles and users tables
from app.startup.create_users import create_users
create_users()
# Setup an application context (since the tests run outside of the webserver context)
the_app.app_context().push()
@pytest.fixture(scope='session')
def app():
return the_app
@pytest.fixture(scope='session')
def db():
"""
Initializes and returns a SQLAlchemy DB object
"""
return the_db
# Copyright 2014 SolidBuilds.com. All rights reserved
#
# Authors: Ling Thio <ling.thio@gmail.com>
from __future__ import print_function # Use print() instead of print
from flask import url_for
def test_page_urls(client):
# Visit home page
response = client.get(url_for('core.home_page'))
assert b'<h1>Home page</h1>' in response.data
# Login as user and visit User page
response = client.post(url_for('user.login'), follow_redirects=True,
data=dict(email='user@example.com', password='Password1'))
assert b'<h1>Home page</h1>' in response.data
response = client.get(url_for('core.user_page'))
assert b'<h1>User page</h1>' in response.data
# Edit User Profile page
response = client.get(url_for('core.user_profile_page'))
assert b'<h1>User Profile</h1>' in response.data
response = client.post(url_for('core.user_profile_page'), follow_redirects=True,
data=dict(first_name='User', last_name='User'))
response = client.get(url_for('core.user_page'))
assert b'<h1>User page</h1>' in response.data
# Logout
response = client.get(url_for('user.logout'), follow_redirects=True)
assert b'<h1>Home page</h1>' in response.data
# Login as admin and visit Admin page
response = client.post(url_for('user.login'), follow_redirects=True,
data=dict(email='admin@example.com', password='Password1'))
assert b'<h1>Home page</h1>' in response.data
response = client.get(url_for('core.admin_page'))
assert b'<h1>Admin page</h1>' in response.data
# Logout
response = client.get(url_for('user.logout'), follow_redirects=True)
assert b'<h1>Home page</h1>' in response.data
[tox]
# Test on the following Python versions
envlist = py27, py33, py34
toxworkdir=../builds/flask_user_starter_app/tox
skipsdist=True
[testenv]
deps = -r{toxinidir}/requirements.txt
# Run automated test suite
commands=
./runtests.sh
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment